Ver código fonte

Do not make Graph subclass of Keyset.

Stefano Cossu 5 anos atrás
pai
commit
c807726c25

+ 1 - 2
lakesuperior/api/resource.py

@@ -7,7 +7,7 @@ from threading import Lock, Thread
 
 import arrow
 
-from rdflib import Graph, Literal, URIRef
+from rdflib import Literal
 from rdflib.namespace import XSD
 
 from lakesuperior.config_parser import config
@@ -16,7 +16,6 @@ from lakesuperior.exceptions import (
 from lakesuperior import env, thread_env
 from lakesuperior.globals import RES_DELETED, RES_UPDATED
 from lakesuperior.model.ldp.ldp_factory import LDP_NR_TYPE, LdpFactory
-from lakesuperior.model.graph.graph import SimpleGraph
 
 
 logger = logging.getLogger(__name__)

+ 11 - 0
lakesuperior/model/base.pxd

@@ -17,3 +17,14 @@ cdef enum:
     QUAD_KLEN = 4 * sizeof(Key)
 
 cdef bytes buffer_dump(Buffer* buf)
+
+# "NULL" key, a value that is never user-provided. Used to mark special
+# values (e.g. deleted records).
+cdef Key NULL_KEY = 0
+
+# Value of first key inserted in an empty term database.
+cdef Key FIRST_KEY = 1
+
+# "NULL" triple, a value that is never user-provided. Used to mark special
+# values (e.g. deleted records).
+cdef TripleKey NULL_TRP = [NULL_KEY, NULL_KEY, NULL_KEY]

+ 5 - 9
lakesuperior/model/graph/callbacks.pxd

@@ -1,14 +1,14 @@
 from libc.stdint cimport uint32_t, uint64_t
 
-from lakesuperior.model.base cimport Buffer
-from lakesuperior.model.graph cimport graph
+from lakesuperior.model.base cimport Buffer, TripleKey
+from lakesuperior.model.graph.graph cimport Graph
 from lakesuperior.model.graph.triple cimport BufferTriple
 
 cdef extern from 'spookyhash_api.h':
     uint64_t spookyhash_64(const void *input, size_t input_size, uint64_t seed)
 
 cdef:
-    bint graph_eq_fn(graph.SimpleGraph g1, graph.SimpleGraph g2)
+    bint graph_eq_fn(Graph g1, Graph g2)
     int term_cmp_fn(const void* key1, const void* key2)
     int trp_cmp_fn(const void* key1, const void* key2)
     size_t term_hash_fn(const void* key, int l, uint32_t seed)
@@ -35,10 +35,6 @@ cdef:
     bint lookup_po_cmp_fn(
         const BufferTriple *trp, const Buffer *t1, const Buffer *t2
     )
-    void add_trp_callback(
-        graph.SimpleGraph gr, const BufferTriple* trp, void* ctx
-    )
-    void del_trp_callback(
-        graph.SimpleGraph gr, const BufferTriple* trp, void* ctx
-    )
+    void add_trp_callback(Graph gr, const TripleKey* spok_p, void* ctx)
+    void del_trp_callback(Graph gr, const TripleKey* spok_p, void* ctx)
 

+ 6 - 6
lakesuperior/model/graph/callbacks.pyx

@@ -7,7 +7,7 @@ cimport lakesuperior.cy_include.collections as cc
 cimport lakesuperior.cy_include.spookyhash as sph
 
 from lakesuperior.model.base cimport Buffer, buffer_dump
-from lakesuperior.model.graph cimport graph
+from lakesuperior.model.graph.graph cimport Graph
 from lakesuperior.model.graph.triple cimport BufferTriple
 
 logger = logging.getLogger(__name__)
@@ -73,7 +73,7 @@ cdef int trp_cmp_fn(const void* key1, const void* key2):
 #    return is_not_equal
 
 
-cdef bint graph_eq_fn(graph.Graph g1, graph.Graph g2):
+cdef bint graph_eq_fn(Graph g1, Graph g2):
     """
     Compare 2 graphs for equality.
 
@@ -222,16 +222,16 @@ cdef inline bint lookup_po_cmp_fn(
 ## LOOKUP CALLBACK FUNCTIONS
 
 cdef inline void add_trp_callback(
-    graph.Graph gr, const TripleKey spok, void* ctx
+    Graph gr, const TripleKey* spok_p, void* ctx
 ):
     """
     Add a triple to a graph as a result of a lookup callback.
     """
-    gr.add(trp)
+    gr.keys.add(spok_p)
 
 
 cdef inline void del_trp_callback(
-    graph.Graph gr, const TripleKey spok, void* ctx
+    Graph gr, const TripleKey* spok_p, void* ctx
 ):
     """
     Remove a triple from a graph as a result of a lookup callback.
@@ -239,6 +239,6 @@ cdef inline void del_trp_callback(
     #logger.info('removing triple: {} {} {}'.format(
         #buffer_dump(trp.s), buffer_dump(trp.p), buffer_dump(trp.o)
     #))
-    gr.remove(spok)
+    gr.keys.remove(spok_p)
 
 

+ 5 - 5
lakesuperior/model/graph/graph.pxd

@@ -4,8 +4,9 @@ from cymem.cymem cimport Pool
 
 cimport lakesuperior.cy_include.collections as cc
 
-from lakesuperior.model.base cimport Buffer
+from lakesuperior.model.base cimport Buffer, TripleKey
 from lakesuperior.model.graph.triple cimport BufferTriple
+from lakesuperior.model.structures.keyset cimport Keyset
 
 # Lookup function that returns whether a triple contains a match pattern.
 # Return True if the triple exists, False otherwise.
@@ -26,14 +27,13 @@ cdef class Graph:
         cc.HashSet *_triples # Set of unique triples.
         # Temp data pool. It gets managed with the object lifecycle via cymem.
         Pool pool
+        Keyset keys
 
         cc.key_compare_ft term_cmp_fn
         cc.key_compare_ft trp_cmp_fn
 
-        BufferTriple* store_triple(self, const BufferTriple* strp)
-        void add_triple(
-            self, const BufferTriple *trp, bint copy=*
-        ) except *
+        void add(self, TripleKey* spok_p) except *
+        void remove(self, TripleKey* spok_p) except *
         int remove_triple(self, const BufferTriple* trp_buf) except -1
         bint trp_contains(self, const BufferTriple* btrp)
 

+ 45 - 95
lakesuperior/model/graph/graph.pyx

@@ -1,9 +1,6 @@
 import logging
 
-from functools import wraps
-
-from rdflib import Graph, URIRef
-from rdflib.term import Node
+import rdflib
 
 from lakesuperior import env
 
@@ -16,7 +13,7 @@ cimport lakesuperior.cy_include.collections as cc
 cimport lakesuperior.model.graph.callbacks as cb
 
 from lakesuperior.model.base cimport Buffer, buffer_dump
-from lakesuperior.model.structures.keyset import Keyset
+from lakesuperior.model.structures.keyset cimport Keyset
 from lakesuperior.model.graph cimport term
 from lakesuperior.model.graph.triple cimport BufferTriple
 from lakesuperior.model.structures.hash cimport term_hash_seed32
@@ -24,7 +21,7 @@ from lakesuperior.model.structures.hash cimport term_hash_seed32
 logger = logging.getLogger(__name__)
 
 
-cdef class Graph(Keyset):
+cdef class Graph:
     """
     Fast and simple implementation of a graph.
 
@@ -39,7 +36,9 @@ cdef class Graph(Keyset):
     ``rdflib.Graph`` instance.
     """
 
-    def __cinit__(self, *args, str uri=None, set data=set(), **kwargs):
+    def __cinit__(
+        self, store, size_t ct=0, str uri=None, set data=set()
+    ):
         """
         Initialize the graph, optionally with Python data.
 
@@ -48,14 +47,29 @@ cdef class Graph(Keyset):
 
         self.pool = Pool()
 
+        if not store:
+            store = env.app_globals.ldprs_store
         # Initialize empty data set.
         if data:
             # Populate with provided Python set.
-            self.add(data)
+            self.keys = Keyset(len(data))
+            self.add_triples(data)
+        else:
+            self.keys = Keyset(ct)
 
 
     ## PROPERTIES ##
 
+    @property
+    def uri(self):
+        """
+        Get resource identifier as a RDFLib URIRef.
+
+        :rtype: rdflib.URIRef.
+        """
+        return rdflib.URIRef(self.id)
+
+
     @property
     def data(self):
         """
@@ -82,7 +96,7 @@ cdef class Graph(Keyset):
 
     def __len__(self):
         """ Number of triples in the graph. """
-        return self._free_i
+        return self.keys.size()
 
 
     def __eq__(self, other):
@@ -168,20 +182,15 @@ cdef class Graph(Keyset):
 
         :rtype: boolean
         """
-        cdef:
-            Buffer ss, sp, so
-            BufferTriple btrp
-
-        btrp.s = &ss
-        btrp.p = &sp
-        btrp.o = &so
+        cdef TripleKey spok
 
-        s, p, o = trp
-        term.serialize_from_rdflib(s, &ss)
-        term.serialize_from_rdflib(p, &sp)
-        term.serialize_from_rdflib(o, &so)
+        spok = [
+            self.store.to_key(trp[0]),
+            self.store.to_key(trp[1]),
+            self.store.to_key(trp[2]),
+        ]
 
-        return self.trp_contains(&btrp)
+        return self.data.contains(&spok)
 
 
     def __iter__(self):
@@ -259,7 +268,7 @@ cdef class Graph(Keyset):
 
         Override in subclasses to accommodate for different init properties.
         """
-        return self.__class__()
+        return self.__class__(self.ct, self.store, uri=self.id)
 
 
     cpdef union_(self, Graph other):
@@ -471,52 +480,6 @@ cdef class Graph(Keyset):
         self |= tmp
 
 
-    cdef inline BufferTriple* store_triple(self, const BufferTriple* strp):
-        """
-        Store triple data in the graph.
-
-        Normally, raw data underlying the triple and terms are only referenced
-        by pointers. If the destination data are garbage collected before the
-        graph is, segfaults are bound to happen.
-
-        This method copies the data to the graph's memory pool, so they are
-        managed with the lifecycle of the graph.
-
-        Note that this method stores items regardless of whether thwy are
-        duplicate or not, so there may be some duplication.
-        """
-        cdef:
-            BufferTriple* dtrp = <BufferTriple*>self.pool.alloc(
-                1, sizeof(BufferTriple)
-            )
-            Buffer* spo = <Buffer*>self.pool.alloc(3, sizeof(Buffer))
-
-        if not dtrp:
-            raise MemoryError()
-        if not spo:
-            raise MemoryError()
-
-        dtrp.s = spo
-        dtrp.p = spo + 1
-        dtrp.o = spo + 2
-
-        spo[0].addr = self.pool.alloc(strp.s.sz, 1)
-        spo[0].sz = strp.s.sz
-        spo[1].addr = self.pool.alloc(strp.p.sz, 1)
-        spo[1].sz = strp.p.sz
-        spo[2].addr = self.pool.alloc(strp.o.sz, 1)
-        spo[2].sz = strp.o.sz
-
-        if not spo[0].addr or not spo[1].addr or not spo[2].addr:
-            raise MemoryError()
-
-        memcpy(dtrp.s.addr, strp.s.addr, strp.s.sz)
-        memcpy(dtrp.p.addr, strp.p.addr, strp.p.sz)
-        memcpy(dtrp.o.addr, strp.o.addr, strp.o.sz)
-
-        return dtrp
-
-
     cdef bint trp_contains(self, const BufferTriple* btrp):
         cdef:
             cc.HashSetIter it
@@ -632,20 +595,21 @@ cdef class Graph(Keyset):
             lookup_fn_t cmp_fn
             cc.HashSetIter it
 
+            TripleKey spok
+
         s, p, o = pattern
 
         # Decide comparison logic outside the loop.
         if s is not None and p is not None and o is not None:
             # Shortcut for 3-term match.
-            trp.s = &ss
-            trp.p = &sp
-            trp.o = &so
-            term.serialize_from_rdflib(s, trp.s, self.pool)
-            term.serialize_from_rdflib(p, trp.p, self.pool)
-            term.serialize_from_rdflib(o, trp.o, self.pool)
-
-            if cc.hashset_contains(self._triples, &trp):
-                callback_fn(gr, &trp, ctx)
+            spok = [
+                self.store.to_key(s),
+                self.store.to_key(p),
+                self.store.to_key(o),
+            ]
+
+            if self.keys.contains(spok):
+                callback_fn(gr, &spok, ctx)
                 return
 
         if s is not None:
@@ -731,30 +695,13 @@ cdef class Imr(Graph):
             s, p, o = item.start, item.stop, item.step
             return self._slice(s, p, o)
 
-        elif isinstance(item, Node):
+        elif isinstance(item, rdflib.Node):
             # If a Node is given, return all values for that predicate.
             return self._slice(self.uri, item, None)
         else:
             raise TypeError(f'Wrong slice format: {item}.')
 
 
-    @property
-    def uri(self):
-        """
-        Get resource identifier as a RDFLib URIRef.
-
-        :rtype: rdflib.URIRef.
-        """
-        return URIRef(self.id)
-
-
-    cdef Imr empty_copy(self):
-        """
-        Create an empty instance carrying over some key properties.
-        """
-        return self.__class__(uri=self.id)
-
-
     def value(self, p, strict=False):
         """
         Get an individual value.
@@ -765,6 +712,9 @@ cdef class Imr(Graph):
             the first found result is returned.
         :rtype: rdflib.term.Node
         """
+        if not self.id:
+            raise ValueError('Cannot use `value` on a non-named graph.')
+
         # TODO use slice.
         values = {trp[2] for trp in self.lookup((self.uri, p, None))}
 

+ 1 - 1
lakesuperior/model/ldp/ldpr.py

@@ -26,7 +26,7 @@ from lakesuperior.dictionaries.srv_mgd_terms import (
 from lakesuperior.exceptions import (
     InvalidResourceError, RefIntViolationError, ResourceNotExistsError,
     ServerManagedTermError, TombstoneError)
-from lakesuperior.model.graph.graph import SimpleGraph, Imr
+from lakesuperior.model.graph.graph import Graph as SimpleGraph, Imr
 from lakesuperior.store.ldp_rs.rsrc_centric_layout import VERS_CONT_LABEL
 from lakesuperior.toolbox import Toolbox
 

+ 1 - 1
lakesuperior/model/structures/keyset.pxd

@@ -19,8 +19,8 @@ cdef class Keyset:
                            # will raise an error.
 
         void seek(self, size_t idx=*)
+        size_t size(self)
         size_t tell(self)
-        bint get_at(self, size_t i, TripleKey* item)
         bint get_next(self, TripleKey* item)
         void add(self, const TripleKey* val) except *
         bint contains(self, const TripleKey* val)

+ 46 - 16
lakesuperior/model/structures/keyset.pyx

@@ -5,7 +5,7 @@ from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
 
 cimport lakesuperior.model.structures.callbacks as cb
 
-from lakesuperior.model.base cimport TripleKey, TRP_KLEN
+from lakesuperior.model.base cimport NULL_TRP, TRP_KLEN, TripleKey
 
 
 logger = logging.getLogger(__name__)
@@ -50,26 +50,21 @@ cdef class Keyset:
         self._cur = idx
 
 
-    cdef size_t tell(self):
+    cdef size_t size(self):
         """
-        Tell the position of the cursor in the keyset.
+        Size of the object as the number of occupied data slots.
+
+        Note that this is different from :py:data:`ct`_, which indicates
+        the number of allocated items in memory.
         """
-        return self._cur
+        return self._free_i
 
 
-    cdef bint get_at(self, size_t i, TripleKey* item):
+    cdef size_t tell(self):
         """
-        Get an item at a given index position. Cython-level method.
-
-        :rtype: TripleKey
+        Tell the position of the cursor in the keyset.
         """
-        if i >= self._free_i:
-            return False
-
-        self._cur = i
-        item[0] = self.data[i]
-
-        return True
+        return self._cur
 
 
     cdef bint get_next(self, TripleKey* item):
@@ -113,6 +108,41 @@ cdef class Keyset:
         self._free_i += 1
 
 
+    cdef void remove(self, const TripleKey* val) except *:
+        """
+        Remove a triple key.
+
+        This method replaces a triple with NULL_TRP if found. It
+        does not reclaim space. Therefore, if many removal operations are
+        forseen, using :py:meth:`subtract`_ is advised.
+        """
+
+        cdef TripleKey stored_val
+
+        self.seek()
+        while self.get_next(&stored_val):
+            if memcmp(val, stored_val, TRP_KLEN) == 0:
+                stored_val[0] = NULL_TRP
+                return
+
+
+    cdef Keyset subtract(self, const Keyset* other):
+        """
+        Create a Keyset by subtracting an``other`` Keyset from the current one.
+
+        :rtype: Keyset
+        """
+        cdef Keyset res = Keyset(self.ct)
+
+        self.seek()
+        while self.get_next(&val):
+            if not other.contains(val):
+                res.add(val)
+        res.resize()
+
+        return res
+
+
     cdef bint contains(self, const TripleKey* val):
         """
         Whether a value exists in the set.
@@ -167,7 +197,7 @@ cdef class Keyset:
         """
         Look up triple keys.
 
-        This works in a similar way that the ``SimpleGraph`` and ``LmdbStore``
+        This works in a similar way that the ``Graph`` and ``LmdbStore``
         methods work.
 
         Any and all the terms may be NULL. A NULL term is treated as unbound.

+ 80 - 81
lakesuperior/store/ldp_rs/lmdb_triplestore.pyx

@@ -1,8 +1,9 @@
 import logging
 import sys
 
+import rdflib
+
 from cython.parallel import prange
-from rdflib import Graph
 from rdflib.graph import DATASET_DEFAULT_GRAPH_ID as RDFLIB_DEFAULT_GRAPH_URI
 
 from lakesuperior.model.graph.graph import Imr
@@ -17,11 +18,11 @@ cimport lakesuperior.cy_include.collections as cc
 cimport lakesuperior.cy_include.cylmdb as lmdb
 
 from lakesuperior.model.base cimport (
-    KLEN, DBL_KLEN, TRP_KLEN, QUAD_KLEN,
+    FIRST_KEY, KLEN, DBL_KLEN, TRP_KLEN, QUAD_KLEN,
     Key, DoubleKey, TripleKey, QuadKey,
     Buffer, buffer_dump
 )
-from lakesuperior.model.graph.graph cimport Graph, Imr
+from lakesuperior.model.graph.graph cimport Graph
 from lakesuperior.model.graph.term cimport Term
 from lakesuperior.model.graph.triple cimport BufferTriple
 
@@ -29,7 +30,6 @@ from lakesuperior.store.base_lmdb_store cimport (
         BaseLmdbStore, data_v, dbi, key_v)
 from lakesuperior.model.graph.term cimport (
         deserialize_to_rdflib, serialize_from_rdflib)
-from lakesuperior.model.structures.keyset cimport Keyset
 from lakesuperior.model.structures.hash cimport (
         HLEN_128 as HLEN, Hash128, hash128)
 
@@ -278,7 +278,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         self._index_triple(IDX_OP_ADD, [spock[0], spock[1], spock[2]])
 
 
-    cpdef add_graph(self, graph):
+    cpdef add_graph(self, c):
         """
         Add a graph to the database.
 
@@ -286,73 +286,41 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         pickled `None` value. This prevents from removing the graph when all
         triples are removed.
 
-        This may be called by read-only operations:
-        https://github.com/RDFLib/rdflib/blob/master/rdflib/graph.py#L1623
-        In which case it needs to open a write transaction. This is not ideal
-        but the only way to handle datasets in RDFLib.
-
         :param rdflib.URIRef graph: URI of the named graph to add.
         """
-        cdef Buffer _sc
-
-        if isinstance(graph, Graph):
-            graph = graph.identifier
-
-        serialize_from_rdflib(graph, &_sc)
-        self._add_graph(&_sc)
-
-
-    cdef void _add_graph(self, Buffer *pk_gr) except *:
-
-        """
-        Add a graph.
-
-        :param pk_gr: Pickled context URIRef object.
-        :type pk_gr: Buffer*
-        """
         cdef:
-            Hash128 chash
+            lmdb.MDB_txn *_txn
+            Buffer _sc
             Key ck
-            lmdb.MDB_txn *tmp_txn
 
-        hash128(pk_gr, &chash)
+        if isinstance(c, rdflib.Graph):
+            c = c.identifier
+
+        ck = self.to_key(c)
         if not self._key_exists(chash, HLEN, b'th:t'):
             # Insert context term if not existing.
             if self.is_txn_rw:
-                tmp_txn = self.txn
+                _txn = self.txn
             else:
-                _check(lmdb.mdb_txn_begin(self.dbenv, NULL, 0, &tmp_txn))
+                _check(lmdb.mdb_txn_begin(self.dbenv, NULL, 0, &_txn))
                 # Open new R/W transactions.
                 #logger.debug('Opening a temporary RW transaction.')
 
             try:
                 #logger.debug('Working in existing RW transaction.')
-                # Use existing R/W transaction.
-                # Main entry.
-                ck = self._append(pk_gr, b't:st', txn=tmp_txn)
-
-                # Index.
-                key_v.mv_data = chash
-                key_v.mv_size = HLEN
-                data_v.mv_data = &ck
-                data_v.mv_size = KLEN
-                _check(lmdb.mdb_put(
-                    tmp_txn, self.get_dbi(b'th:t'), &key_v, &data_v, 0
-                ))
-
                 # Add to list of contexts.
                 key_v.mv_data = &ck
                 key_v.mv_size = KLEN
                 data_v.mv_data = &ck # Whatever, length is zero anyways
                 data_v.mv_size = 0
                 _check(lmdb.mdb_put(
-                    tmp_txn, self.get_dbi(b'c:'), &key_v, &data_v, 0
+                    _txn, self.get_dbi(b'c:'), &key_v, &data_v, 0
                 ))
                 if not self.is_txn_rw:
-                    _check(lmdb.mdb_txn_commit(tmp_txn))
+                    _check(lmdb.mdb_txn_commit(_txn))
             except:
                 if not self.is_txn_rw:
-                    lmdb.mdb_txn_abort(tmp_txn)
+                    lmdb.mdb_txn_abort(_txn)
                 raise
 
 
@@ -658,7 +626,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                 _check(lmdb.mdb_cursor_get(cur, &key_v, &data_v, lmdb.MDB_SET))
                 while True:
                     c_uri = self.from_key((<Key*>data_v.mv_data)[0])
-                    contexts.append(Imr(uri=c_uri, store=self))
+                    contexts.append(rdflib.Graph(self, uri=c_uri, store=self))
                     try:
                         _check(lmdb.mdb_cursor_get(
                             cur, &key_v, &data_v, lmdb.MDB_NEXT_DUP))
@@ -677,7 +645,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             self._cur_close(cur)
 
 
-    cdef Keyset triple_keys(self, tuple triple_pattern, context=None, uri):
+    cdef Graph triple_keys(self, tuple triple_pattern, context=None, uri):
         """
         Top-level lookup method.
 
@@ -689,21 +657,20 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         :param context: Context graph or URI, or None.
         :type context: rdflib.term.Identifier or None
         """
-        # TODO: Improve performance by allowing passing contexts as a tuple.
         cdef:
             size_t ct = 0, i = 0
             lmdb.MDB_cursor *icur
             lmdb.MDB_val key_v, data_v
             Key tk, ck
             TripleKey spok
-            Keyset flt_res, ret
+            Graph flt_res, ret
 
         if context is not None:
             try:
                 ck = self.to_key(context)
             except KeyNotFoundError:
                 # Context not found.
-                return Keyset()
+                return Graph(self)
 
             icur = self._cur_open('c:spo')
 
@@ -718,7 +685,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                             tk = self.to_key(term)
                         except KeyNotFoundError:
                             # A term key was not found.
-                            return Keyset()
+                            return Graph(self)
                         spok[i] = tk
                     data_v.mv_data = spok
                     data_v.mv_size = TRP_KLEN
@@ -728,8 +695,8 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                     except KeyNotFoundError:
                         # Triple not found.
                         #logger.debug('spok / ck pair not found.')
-                        return Keyset()
-                    ret = Keyset(1)
+                        return Graph(self)
+                    ret = Graph(self, 1)
                     ret.add(&spok)
 
                     return ret
@@ -742,10 +709,10 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                             icur, &key_v, &data_v, lmdb.MDB_SET))
                     except KeyNotFoundError:
                         # Triple not found.
-                        return Keyset()
+                        return Graph(self)
 
                     _check(lmdb.mdb_cursor_count(icur, &ct))
-                    ret = Keyset(ct)
+                    ret = Graph(self, ct)
 
                     _check(lmdb.mdb_cursor_get(
                         icur, &key_v, &data_v, lmdb.MDB_GET_MULTIPLE))
@@ -767,13 +734,13 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                     try:
                         res = self._lookup(triple_pattern)
                     except KeyNotFoundError:
-                        return Keyset()
+                        return Graph(self)
 
                     key_v.mv_data = &ck
                     key_v.mv_size = KLEN
                     data_v.mv_size = TRP_KLEN
 
-                    flt_res = Keyset(res.ct)
+                    flt_res = Graph(self, res.ct)
                     res.seek()
                     while res.get_next(&spok):
                         data_v.mv_data = spok
@@ -785,7 +752,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                         except KeyNotFoundError:
                             continue
                         else:
-                            flt_res.add(&spok)
+                            flt_res.keys.add(&spok)
 
                     return flt_res
             finally:
@@ -796,11 +763,11 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             try:
                 res = self._lookup(triple_pattern)
             except KeyNotFoundError:
-                return Keyset()
+                return Graph(self)
             return res
 
 
-    cdef Keyset _lookup(self, tuple triple_pattern):
+    cdef Graph _lookup(self, tuple triple_pattern):
         """
         Look up triples in the indices based on a triple pattern.
 
@@ -824,7 +791,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             if o is not None:
                 ok = self.to_key(o)
         except KeyNotFoundError:
-            return Keyset()
+            return Graph(self)
 
         if s is not None:
             tk1 = sk
@@ -840,10 +807,10 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                         _check(lmdb.mdb_get(
                             self.txn, self.get_dbi('spo:c'), &spok_v, &ck_v))
                     except KeyNotFoundError:
-                        return Keyset()
+                        return Graph(self)
 
-                    matches = Keyset(1)
-                    matches.add(&spok)
+                    matches = Graph(self, 1)
+                    matches.data.add(&spok)
                     return matches
 
                 # s p ?
@@ -880,10 +847,10 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                 ), 'Error gathering DB stats.'
             )
             ct = db_stat.ms_entries
-            ret = Keyset(ct)
+            ret = Graph(self, ct)
             #logger.debug(f'Triples found: {ct}')
             if ct == 0:
-                return Keyset()
+                return Graph(self)
 
             _check(lmdb.mdb_cursor_get(
                     dcur, &key_v, &data_v, lmdb.MDB_FIRST))
@@ -902,7 +869,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             self._cur_close(dcur)
 
 
-    cdef Keyset _lookup_1bound(self, unsigned char idx, Key luk):
+    cdef Graph _lookup_1bound(self, unsigned char idx, Key luk):
         """
         Lookup triples for a pattern with one bound term.
 
@@ -936,7 +903,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             _check(lmdb.mdb_cursor_count(icur, &ct))
 
             # Allocate memory for results.
-            ret = Keyset(ct)
+            ret = Graph(self, ct)
 
             _check(lmdb.mdb_cursor_get(icur, &key_v, &data_v, lmdb.MDB_SET))
             _check(lmdb.mdb_cursor_get(
@@ -948,7 +915,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                     spok[term_order[1]] = lu_dset[i][0]
                     spok[term_order[2]] = lu_dset[i][1]
 
-                    ret.add(&spok)
+                    ret.data.add(&spok)
 
                 try:
                     # Get results by the page.
@@ -961,7 +928,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             self._cur_close(icur)
 
 
-    cdef Keyset _lookup_2bound(
+    cdef Graph _lookup_2bound(
         self, unsigned char idx1, unsigned char idx2, DoubleKey tks
     ):
         """
@@ -980,7 +947,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             unsigned char term_order[3] # Lookup ordering
             size_t ct, i = 0
             lmdb.MDB_cursor* icur
-            Keyset ret
+            Graph ret
             DoubleKey luk
             TripleKey spok
 
@@ -1016,7 +983,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
             # Count duplicates for key and allocate memory for result set.
             _check(lmdb.mdb_cursor_get(icur, &key_v, &data_v, lmdb.MDB_SET))
             _check(lmdb.mdb_cursor_count(icur, &ct))
-            ret = Keyset(ct)
+            ret = Graph(self, ct)
 
             _check(lmdb.mdb_cursor_get(icur, &key_v, &data_v, lmdb.MDB_SET))
             _check(lmdb.mdb_cursor_get(
@@ -1028,7 +995,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
                     spok[term_order[1]] = luk[1]
                     spok[term_order[2]] = lu_dset[i]
 
-                    ret.add(&spok)
+                    ret.data.add(&spok)
 
                 try:
                     # Get results by the page.
@@ -1254,7 +1221,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
 
     cdef inline Key to_key(self, term) except -1:
         """
-        Convert a term into a key index (bare number).
+        Convert a term into a key and insert it in the term key store.
 
         :param rdflib.Term term: An RDFLib term (URIRef, BNode, Literal).
         :param Key key: Key that will be produced.
@@ -1262,6 +1229,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         :rtype: void
         """
         cdef:
+            lmdb.MDB_txn *_txn
             Hash128 thash
             Buffer pk_t
 
@@ -1271,10 +1239,41 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         key_v.mv_data = thash
         key_v.mv_size = HLEN
 
-        dbi = self.get_dbi('th:t')
-        _check(lmdb.mdb_get(self.txn, dbi, &key_v, &data_v))
+        try:
+            _check(lmdb.mdb_get(
+                self.txn, self.get_dbi(b'th:t'), &key_v, &data_v)
+            )
+
+            return (<Key*>data_v.mv_data)[0]
+        except KeyNotFoundError:
+            # If key is not in the store, add it.
+            if self.is_txn_rw:
+                # Use existing R/W transaction.
+                #logger.debug('Working in existing RW transaction.')
+                _txn = self.txn
+            else:
+                # Open new R/W transaction.
+                #logger.debug('Opening a temporary RW transaction.')
+                _check(lmdb.mdb_txn_begin(self.dbenv, NULL, 0, &_txn))
+
+            try:
+                # Main entry.
+                ck = self._append(pk_gr, b't:st', txn=_txn)
 
-        return (<Key*>data_v.mv_data)[0]
+                # Index.
+                data_v.mv_data = &ck
+                data_v.mv_size = KLEN
+                _check(lmdb.mdb_put(
+                    _txn, self.get_dbi(b'th:t'), &key_v, &data_v, 0
+                ))
+                if not self.is_txn_rw:
+                    _check(lmdb.mdb_txn_commit(_txn))
+
+                return ck
+            except:
+                if not self.is_txn_rw:
+                    lmdb.mdb_txn_abort(_txn)
+                raise
 
 
     cdef Key _append(
@@ -1304,7 +1303,7 @@ cdef class LmdbTriplestore(BaseLmdbStore):
         try:
             _check(lmdb.mdb_cursor_get(cur, &key_v, NULL, lmdb.MDB_LAST))
         except KeyNotFoundError:
-            new_idx = 0
+            new_idx = FIRST_KEY
         else:
             new_idx = (<Key*>key_v.mv_data)[0] + 1
             logger.debug(f'New index value: {new_idx}')

+ 7 - 7
lakesuperior/store/ldp_rs/rsrc_centric_layout.py

@@ -9,7 +9,7 @@ from urllib.parse import urldefrag
 
 import arrow
 
-from rdflib import Dataset, Graph, Literal, URIRef, plugin
+from rdflib import Dataset, Literal, URIRef, plugin
 from rdflib.compare import to_isomorphic
 from rdflib.namespace import RDF
 from rdflib.query import ResultException
@@ -24,7 +24,7 @@ from lakesuperior.dictionaries.srv_mgd_terms import  srv_mgd_subjects, \
 from lakesuperior.globals import ROOT_RSRC_URI
 from lakesuperior.exceptions import (InvalidResourceError,
         ResourceNotExistsError, TombstoneError, PathSegmentError)
-from lakesuperior.model.graph.graph import SimpleGraph, Imr
+from lakesuperior.model.graph.graph import Graph, Imr
 
 
 META_GR_URI = nsc['fcsystem']['meta']
@@ -223,8 +223,8 @@ class RsrcCentricLayout:
             #import pdb; pdb.set_trace()
             imr = self.get_imr('/', incl_inbound=False, incl_children=True)
 
-        gr = Graph(identifier=imr.uri)
-        gr += imr.data
+        #gr = Graph(identifier=imr.uri)
+        #gr += imr.data
         #checksum = to_isomorphic(gr).graph_digest()
         #digest = sha256(str(checksum).encode('ascii')).digest()
 
@@ -250,7 +250,7 @@ class RsrcCentricLayout:
         :param rdflib.term.URIRef ctx: URI of the optional context. If None,
             all named graphs are queried.
 
-        :rtype: SimpleGraph
+        :rtype: Graph
         """
         return self.store.triple_keys((subject, None, None), ctx)
 
@@ -299,7 +299,7 @@ class RsrcCentricLayout:
 
         # Include inbound relationships.
         if incl_inbound and len(imr):
-            gr = SimpleGraph({*self.get_inbound_rel(nsc['fcres'][uid])})
+            gr = Graph({*self.get_inbound_rel(nsc['fcres'][uid])})
             imr |= gr
 
         if strict:
@@ -369,7 +369,7 @@ class RsrcCentricLayout:
         Get all metadata about a resource's versions.
 
         :param string uid: Resource UID.
-        :rtype: SimpleGraph
+        :rtype: Graph
         """
         # **Note:** This pretty much bends the ontology—it replaces the graph
         # URI with the subject URI. But the concepts of data and metadata in