Browse Source

Store graph; get contexts or MDB match patterns.

Stefano Cossu 4 years ago
parent
commit
2486b0c3bf
17 changed files with 525 additions and 321 deletions
  1. 31 0
      cpython/py_graph.h
  2. 2 2
      include/buffer.h
  3. 1 7
      include/core.h
  4. 41 3
      include/graph.h
  5. 42 26
      include/store_mdb.h
  6. 1 1
      setup.py
  7. 1 1
      src/codec/nt_grammar.y
  8. 13 13
      src/core.c
  9. 1 1
      src/environment.c
  10. 130 99
      src/graph.c
  11. 5 5
      src/store_htable.c
  12. 176 127
      src/store_mdb.c
  13. 7 6
      test.c
  14. 2 2
      test/test.h
  15. 1 2
      test/test_codec_nt.c
  16. 47 4
      test/test_graph.c
  17. 24 22
      test/test_store_mdb.c

+ 31 - 0
cpython/py_graph.h

@@ -202,6 +202,33 @@ Graph_copy (PyTypeObject *cls, PyObject *src)
 
     Py_INCREF(res);
     return (PyObject *) res;
+};
+
+
+static PyObject *
+Graph_store (PyObject *self)
+{
+    /*
+    PyObject *dest_obj = PyObject_CallFunction (self, "b", LSUP_STORE_MDB);
+    if (UNLIKELY (!dest_obj)) {
+        PyErr_SetString (PyExc_SystemError, "Error Creating stored graph.");
+        return NULL;
+    }
+    */
+    GraphObject *dest_obj = (GraphObject *) Py_TYPE (self)->tp_alloc(
+            Py_TYPE (self), 0);
+    if (!dest_obj) return PyErr_NoMemory();
+
+    LSUP_rc rc = LSUP_graph_store (
+            ((GraphObject *) self)->ob_struct, &((dest_obj)->ob_struct), NULL);
+    if (rc != LSUP_OK) {
+        log_error (LSUP_strerror (rc));
+        PyErr_SetString (PyExc_SystemError, "Error storing graph.");
+        return NULL;
+    };
+
+    Py_INCREF (dest_obj);
+    return (PyObject *)dest_obj;
 }
 
 
@@ -479,6 +506,10 @@ static PyMethodDef Graph_methods[] = {
         "from_rdf", (PyCFunction) Graph_new_from_rdf,
         METH_CLASS | METH_VARARGS, "Create a graph from a RDF file."
     },
+    {
+        "store", (PyCFunction) Graph_store, METH_NOARGS,
+        "Store a graph into the permanent back end."
+    },
     {"add", (PyCFunction) Graph_add, METH_O, "Add triples to a graph."},
     {
         "remove", (PyCFunction) Graph_remove, METH_VARARGS,

+ 2 - 2
include/buffer.h

@@ -68,8 +68,8 @@ LSUP_buffer_init (LSUP_Buffer *buf, const size_t size, const void *data);
 inline LSUP_Buffer *
 LSUP_buffer_new (const size_t size, const void *data)
 {
-    LSUP_Buffer *buf = calloc (1, sizeof (*buf));
-    if (!buf) return NULL;
+    LSUP_Buffer *buf;
+    CALLOC_GUARD (buf, NULL);
 
     if (LSUP_buffer_init (buf, size, data) != LSUP_OK) {
         free (buf->addr);

+ 1 - 7
include/core.h

@@ -74,7 +74,7 @@ typedef int LSUP_rc;
 #define LSUP_MEM_ERR        -88892
 #define LSUP_CONFLICT_ERR   -88891
 // NOTE When adding new error codes, use a value larger than the last one
-// in the list. Also change LSUP_MAX_ERR.
+// in the list. Also change LSUP_MAX_ERROR.
 
 #define LSUP_MIN_ERROR      LSUP_ERROR
 #define LSUP_MAX_ERROR      LSUP_CONFLICT_ERR
@@ -98,12 +98,6 @@ typedef LSUP_Key LSUP_QuadKey[4];
 typedef char uuid_str_t[UUIDSTR_SIZE];
 
 
-/*
-// Yes, a textbook mistake; but writing min and max for all int types is crazy.
-#define min(x, y) (x) < (y) ? (x) : (y)
-#define max(x, y) (x) > (y) ? (x) : (y)
-*/
-
 /** @brief Make recursive directories.
  *
  * from https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950

+ 41 - 3
include/graph.h

@@ -44,7 +44,7 @@ typedef struct GraphIterator LSUP_GraphIterator;
  * use #LSUP_graph_set_namespace(). MDB graphs use a persistent namespace map
  * that is common to all the graphs in the same store. This cannot be changed.
  *
- * @param store_type[in] TYpe of store for the graph. One of the values of
+ * @param store_type[in] Type of store for the graph. One of the values of
  *  #LSUP_store_type.
  *
  * @param gr[out] Pointer to a pointer to the new graph. It must be freed with
@@ -63,6 +63,32 @@ LSUP_graph_new_env (const LSUP_Env *env, const LSUP_store_type store_type);
 #define LSUP_graph_new(type) LSUP_graph_new_env (LSUP_default_env, type)
 
 
+/** @brief Create an array of graph from triples matching a pattern in a store.
+ *
+ * The s, p, o lookup terms behave as in #LSUP_store_lookup().
+ *
+ * @param[in] s Lookup subject.
+ *
+ * @param[in] p Lookup predicate.
+ *
+ * @param[in] o Lookup object.
+ *
+ * @param[in] env Environment to look into. If NULL, it is set to the deafult
+ *  environment.
+ *
+ * @return A NULL-terminated array of graphs.
+ */
+LSUP_Graph **
+LSUP_graph_new_lookup_env (
+        const LSUP_Graph *gr, const LSUP_Env *env, const LSUP_Term *s,
+        const LSUP_Term *p, const LSUP_Term *o);
+
+/** @brief Shortcut for #LSUP_graph_new_lookup_env() on default MDB store.
+ */
+#define LSUP_graph_new_lookup (s, p, o) \
+    LSUP_graph_new_lookup_env (LSUP_default_env, (s), (p), (o))
+
+
 /** @brief copy a graph into a new one.
  *
  * The new graph is compacted to the minimum required size.
@@ -90,16 +116,20 @@ LSUP_graph_copy (const LSUP_Graph *src);
  *
  * @param[in] src Graph to store.
  *
+ * @param[out] dest Pointer to graph handle for the new stored graph. The new
+ *  graph will have the same URI as the source.
+ *
  * @param[in] env Environment to copy to. If NULL, it is set to the deafult
  *  LSUP store. This makes it possible to copy MDB graphs across different
- *  envirnments. If the source is a MDB graph and the environment is the same
+ *  environments. If the source is a MDB graph and the environment is the same
  *  as the source, no change occurs.
  *
  * @return LSUP_OK on success; LSUP_NOACTION if the graph is already stored in
  *  the same enviroment; <0 on error.
  */
 LSUP_rc
-LSUP_graph_store (const LSUP_Graph *src, const LSUP_Env *env);
+LSUP_graph_store (
+        const LSUP_Graph *src, LSUP_Graph **dest_p, const LSUP_Env *env);
 
 
 /** Perform a boolean operation between two graphs.
@@ -147,10 +177,18 @@ LSUP_graph_uri (const LSUP_Graph *gr);
 
 
 /** Set the URI of a graph.
+ *
+ * Note that by changing the URI of a graph backed by a context-sensitive store
+ * (i.e. LSUP_STORE_MDB*) effectively changes the underlying data set that the
+ * graph points to. Triples are looked up in, and added to, the context that
+ * the graph URI represents. A non-context graph retains the same triple set
+ * when graph URI changes.
  *
  * @param gr[in] Graph whose URI is to be changed.
  *
  * @param uri[in] New URI as a string. If NULL, a UUID4 URN is generated.
+ *
+ * @return LSUP_OK on success; <0 on error.
  */
 LSUP_rc
 LSUP_graph_set_uri (LSUP_Graph *gr, const char *uri);

+ 42 - 26
include/store_mdb.h

@@ -28,8 +28,8 @@
 
 
 typedef char DbLabel[8];
-typedef struct MDBStore LSUP_MDBStore;
-typedef struct MDBIterator LSUP_MDBIterator;
+typedef struct mdbstore_t LSUP_MDBStore;
+typedef struct mdbstore_iter_t LSUP_MDBIterator;
 
 typedef LSUP_rc (*store_match_fn_t)(const LSUP_TripleKey spok, void *data);
 
@@ -144,7 +144,7 @@ LSUP_mdbstore_add_init (LSUP_MDBStore *store, const LSUP_Buffer *sc);
  *  already existed; LSUP_DB_ERR if an MDB error occurred.
  */
 LSUP_rc
-LSUP_mdbstore_add_iter (struct MDBIterator *it, const LSUP_SerTriple *sspo);
+LSUP_mdbstore_add_iter (LSUP_MDBIterator *it, const LSUP_SerTriple *sspo);
 
 
 /** @brief Finalize an add loop and free iterator.
@@ -193,7 +193,7 @@ LSUP_mdbstore_add_abort (LSUP_MDBIterator *it);
  * @param data_size[in] Number of triples to be inserted.
  */
 LSUP_rc LSUP_mdbstore_add(
-        struct MDBStore *store, const LSUP_Buffer *sc,
+        LSUP_MDBStore *store, const LSUP_Buffer *sc,
         const LSUP_SerTriple strp[], const size_t ct, size_t *inserted);
 
 
@@ -217,26 +217,26 @@ LSUP_mdbstore_remove(
  * Any and all of the terms may be NULL, which indicates an unbound query
  * term. Stores with context not set will always ignore the fourth term.
  *
- * @param store[in] The store to be queried.
+ * @param[in] store The store to be queried.
  *
- * @param ss Buffer representing the serialized s term.
+ * @param[in] ss Buffer representing the serialized s term.
  *
- * @param sp Buffer representing the serialized p term.
+ * @param[in] sp Buffer representing the serialized p term.
  *
- * @param so Buffer representing the serialized o term.
+ * @param[in] so Buffer representing the serialized o term.
  *
- * @param sc Serialized context to limit search to. It may be NULL, in which
+ * @param[in] sc Serialized context to limit search to. It may be NULL, in which
  *  case search is done in all contexts. Note that triples inserted without
  *  context are assigned the *default* context, indicated by the "default_ctx"
  *  member of the store struct.
  *
- * @param it[out] Pointer to a pointer to an #LSUP_MDBIterator that will be
- * populated with a result iterator. This is always created even if no matches
- * are found and must be freed with #LSUP_mdbiter_free after use. If matches
- * are found, the iterator points to the first result which can be retrieved
- * with #LSUP_mdbiter_next.
+ * @param[out] it Pointer to an #LSUP_MDBIterator handle that will be populated
+ * with a result iterator. This is always created even if no matches are found
+ * and must be freed with #LSUP_mdbiter_free() after use. If matches are found,
+ * the iterator points to the first result which can be retrieved with
+ * #LSUP_mdbiter_next().
  *
- * @param ct[out] If not NULL, this will be populated with the number of
+ * @param[out] ct If not NULL, this will be populated with the number of
  *  entries found. It is very inexpensive to set for lookups without context,
  *  much less so for 1-bound and 2-bound context lookups, in which cases it
  *  should be set only if needed.
@@ -271,7 +271,8 @@ LSUP_mdbstore_lookup(
  * @return LSUP_OK if results were found; LSUP_END if no (more) results were
  * found; LSUP_DB_ERR if a MDB_* error occurred.
  */
-LSUP_rc LSUP_mdbiter_next (LSUP_MDBIterator *it, LSUP_SerTriple *sspo);
+LSUP_rc LSUP_mdbiter_next (
+        LSUP_MDBIterator *it, LSUP_SerTriple *sspo, LSUP_Buffer **ctx);
 
 
 /** @brief Iterator's internal counter.
@@ -291,25 +292,40 @@ LSUP_mdbiter_cur (LSUP_MDBIterator *it);
  *
  * @param it[in] Iterator pointer. It will be set to NULL after freeing.
  */
-void LSUP_mdbiter_free (struct MDBIterator *it);
+void LSUP_mdbiter_free (LSUP_MDBIterator *it);
 
 
-/** @brief Contexts that a triple key appears in.
+/** @brief Gather the contexts of all triples matching a pattern.
+ *
+ * This function yields a NULL-terminated array of LSUP_Buffer handles for all
+ * the triples that match an s, p, o lookup pattern in a store. All values are
+ * unique.
+ *
+ * TODO Implement a free method (and align names). Currently freeing the result
+ * is non-trivial and easy to forget:
  *
- * This function is most conveniently used by a callback to
- * #LSUP_mdbstore_lookup because it handles triple keys.
+ *      size_t i = 0;
+ *      while (ctx_a[i] != NULL)
+ *          free (ctx_a[i++]); // Buffer data are memory-mapped. Not freeing.
+ *      free (ctx_a);
+ *
+ * ss, sp, so arguments are used as in #LSUP_mdbstore_lookup().
  *
  * @param store[in] The store to be queried.
  *
- * @param spok[in] Triple key to look up.
+ * @param ss[in] Serialized subject. It may be NULL.
+ *
+ * @param sp[in] Serialized predicate. It may be NULL.
  *
- * @param ck[out] Pointer to an array of contexts. Memory is allocated by this
- * function and must be freed by the caller.
+ * @param so[in] Serialized object. It may be NULL.
  *
- * @param ct[out] Number of contexts found.
+ * @return Array of context handles. Memory is allocated by this function and
+ * must be freed by the caller.
  */
-LSUP_rc LSUP_mdbstore_triple_contexts(
-        LSUP_MDBStore *store, LSUP_Key spok[], LSUP_Key **ck, size_t *ct);
+LSUP_Buffer **
+LSUP_mdbstore_lookup_contexts (
+        LSUP_MDBStore *store, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
+        const LSUP_Buffer *so);
 
 
 /** @brief Get all namespace prefixes in the store.

+ 1 - 1
setup.py

@@ -21,7 +21,7 @@ sources = (
     ]
 )
 
-debug = True
+debug = False
 
 compile_args = [
     # '-std=c99',

+ 1 - 1
src/codec/nt_grammar.y

@@ -30,7 +30,7 @@
 
 // Rules.
 
-ntriplesDoc ::= triples EOF. { log_debug ("Parsed N-Triples document."); }
+ntriplesDoc ::= triples EOF.
 
 triples     ::= eol.
 triples     ::= triple eol.

+ 13 - 13
src/core.c

@@ -9,10 +9,10 @@
  * warning_msg[rc - LSUP_MIN_WARNING].
  */
 char *warning_msg[] = {
-    "No action or change of state occurred.",
-    "No result.",
-    "End of the loop reached.",
-    "A resource conflict prevented some actions from being completed.",
+    "LSUP_NOACTION: No action or change of state occurred.",
+    "LSUP_NORESULT: No result.",
+    "LSUP_END: End of the loop reached.",
+    "LSUP_CONFLICT: A resource conflict prevented an action from completing."
 };
 
 /*
@@ -20,15 +20,15 @@ char *warning_msg[] = {
  * err_msg[rc - LSUP_MIN_ERROR].
  */
 char *err_msg[] = {
-    "LSUP runtime error.",
-    "Error parsing input.",
-    "Invalid input.",
-    "MDB transaction error.",
-    "Database error.",
-    "Not implemented.",
-    "Input/Output error.",
-    "Memory error.",
-    "A resource conflict resulted in an invalid state.",
+    "LSUP_ERROR: Geneic LSUP runtime error.",
+    "LSUP_PARSE_ERR: Error parsing input.",
+    "LSUP_VALUE_ERR: Invalid input.",
+    "LSUP_TXN_ERR: MDB transaction error.",
+    "LSUP_DB_ERR: Database error.",
+    "LSUP_NOT_IMPL_ERR: Feature is not implemented.",
+    "LSUP_IO_ERR: Input/Output error.",
+    "LSUP_MEM_ERR: Memory error.",
+    "LSUP_CONFLICT_ERR: A resource conflict resulted in an invalid state.",
 };
 
 

+ 1 - 1
src/environment.c

@@ -62,7 +62,7 @@ LSUP_init (void)
     if (LSUP_default_env == NULL) {
 #ifdef DEBUG
         // In debug mode, always use max logging.
-        int loglevel = LOG_TRACE;
+        int loglevel = LOG_DEBUG;
 #else
         char *_loglevel = getenv ("LSUP_LOGLEVEL");
         int loglevel = (_loglevel == NULL) ? LOG_INFO : atoi (_loglevel);

+ 130 - 99
src/graph.c

@@ -2,33 +2,24 @@
 #include "store_mdb.h"
 #include "graph.h"
 
-// Initial size of lookup graph. It will double each time capacity is reached.
-#define LOOKUP_GR_INIT_SIZE 64
-
-// Expand hash table memory by this factor to keep a good load factor.
-#define PREALLOC_FACTOR 1.4
-
-// Assume VERY coarsly that the number of unique terms will be in general
-// 1.7 times the number of triples. This is conservative to maintain load
-// factor low.
-#define IDX_SIZE_RATIO 1.7
-
-typedef enum KSetFlag {
-    LSUP_KS_NONE        = 0,
-    LSUP_KS_CHECK_CAP   = 1 << 0,
-    LSUP_KS_CHECK_DUP   = 1 << 1,
-} KSetFlag;
 
+/*
+ * Data types.
+ */
 
 typedef struct Graph {
     const LSUP_Env *        env;            // LSUP environment.
     LSUP_store_type         store_type;     // Back end type: in-memory or MDB.
     LSUP_Term               *uri;           // Graph "name" (URI)
-    union {                                 // Back end, defined by store_type.
+    union {
         LSUP_HTStore *      ht_store;
         LSUP_MDBStore *     mdb_store;
-    };
-    LSUP_NSMap *        nsm;            // Namespace map.
+    };                                      // Back end, defined by store_type.
+    LSUP_NSMap *            nsm;            // Namespace map. NOTE: This is
+                                            // NULL for MDB* stores. To get a
+                                            // proper NSMap, always call
+                                            // #LSUP_graph_namespace() for all
+                                            // types of graphs.
 } Graph;
 
 
@@ -42,34 +33,15 @@ typedef struct GraphIterator {
 } GraphIterator;
 
 
-/**
- * Extern inline functions.
+/*
+ * Static prototypes.
  */
-size_t LSUP_graph_size (const LSUP_Graph *gr);
-
 
-/* * * Post-lookup callback prototypes * * */
-
-int match_add_fn(
-        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
-        void *ctx);
-
-
-int match_rm_fn(
-        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
-        void *ctx);
-
-static LSUP_rc
+inline static LSUP_rc
 graph_iter_next_buffer (GraphIterator *it, LSUP_SerTriple *sspo);
 
-
-static inline bool is_null_trp (const LSUP_TripleKey *trp)
-{
-    return (
-            *trp[0] == NULL_KEY &&
-            *trp[1] == NULL_KEY &&
-            *trp[2] == NULL_KEY);
-}
+static LSUP_rc
+graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest);
 
 #define ENTRY(a, b) (be) == (LSUP_STORE_##a) ||
 static inline bool
@@ -78,7 +50,9 @@ check_backend (LSUP_store_type be)
 #undef ENTRY
 
 
-/* * * GRAPH * * */
+/*
+ * Graph API.
+ */
 
 Graph *
 LSUP_graph_new_env (const LSUP_Env *env, const LSUP_store_type store_type)
@@ -113,30 +87,38 @@ LSUP_graph_new_env (const LSUP_Env *env, const LSUP_store_type store_type)
 }
 
 
-/**
- * Copy triples from a source graph into a destination one.
- *
- * The destination graph is not initialized here, so the copy is cumulative.
- */
-static LSUP_rc
-graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest)
+LSUP_Graph **
+LSUP_graph_new_lookup_env (
+        const LSUP_Graph *gr, const LSUP_Env *env, const LSUP_Term *s,
+        const LSUP_Term *p, const LSUP_Term *o)
 {
-    LSUP_rc rc = LSUP_NOACTION;
+    LSUP_Buffer *ss = LSUP_buffer_new_from_term (s);
+    LSUP_Buffer *sp = LSUP_buffer_new_from_term (p);
+    LSUP_Buffer *so = LSUP_buffer_new_from_term (o);
 
-    GraphIterator *it = LSUP_graph_lookup (src, NULL, NULL, NULL, NULL);
+    LSUP_Buffer **ctx_a = LSUP_mdbstore_lookup_contexts (
+            gr->mdb_store, ss, sp, so);
+    if (UNLIKELY (!ctx_a)) return NULL;
 
-    LSUP_SerTriple sspo;
+    LSUP_buffer_free (ss);
+    LSUP_buffer_free (sp);
+    LSUP_buffer_free (so);
 
-    LSUP_GraphIterator *add_it = LSUP_graph_add_init (dest);
-    while (graph_iter_next_buffer (it, &sspo) != LSUP_END) {
-        log_debug ("Inserting triple #%lu", LSUP_graph_iter_cur (it));
-        LSUP_rc add_rc = LSUP_graph_add_iter (add_it, &sspo);
-        if (LIKELY (add_rc == LSUP_OK)) rc = LSUP_OK;
-        else if (add_rc < 0) return add_rc;
+    // Count for allocation.
+    size_t i;
+    for (i = 0; ctx_a[i] != NULL; i++) {}
+    LSUP_Graph **gr_a = calloc (i + 1, sizeof (*gr_a));
+    if (UNLIKELY (!gr_a)) return NULL;
+
+    for (i = 0; ctx_a[i] != NULL; i++) {
+        gr_a[i] = LSUP_graph_new (LSUP_STORE_MDB);
+        LSUP_Term *uri = LSUP_term_new_from_buffer (ctx_a[i]);
+        gr_a[i]->uri = uri;
+        LSUP_buffer_free (ctx_a[i]);
     }
-    LSUP_graph_add_done (it);
+    free (ctx_a);
 
-    return rc;
+    return gr_a;
 }
 
 
@@ -154,7 +136,8 @@ LSUP_graph_copy (const Graph *src)
 
 
 LSUP_rc
-LSUP_graph_store (const LSUP_Graph *src, const LSUP_Env *env)
+LSUP_graph_store (
+        const LSUP_Graph *src, LSUP_Graph **dest_p, const LSUP_Env *env)
 {
     if (!env) env = LSUP_default_env;
     if (src->store_type == LSUP_STORE_MDB && src->env == env)
@@ -163,8 +146,23 @@ LSUP_graph_store (const LSUP_Graph *src, const LSUP_Env *env)
     LSUP_Graph *dest = LSUP_graph_new_env (env, LSUP_STORE_MDB);
     if (UNLIKELY (!dest)) return LSUP_DB_ERR;
 
-    LSUP_rc rc = graph_copy_contents (src, dest);
-    if (UNLIKELY (rc != LSUP_OK)) return LSUP_DB_ERR;
+    char *uri_str = strdup(src->uri->data);
+    if (UNLIKELY (!uri_str)) return LSUP_MEM_ERR;
+
+    LSUP_term_done (dest->uri);
+    LSUP_uri_init (dest->uri, uri_str);
+    free (uri_str);
+
+    LSUP_rc rc;
+    rc = graph_copy_contents (src, dest);
+    if (UNLIKELY (rc < 0)) return LSUP_DB_ERR;
+
+    if (src->store_type == LSUP_STORE_MEM) {
+        rc = LSUP_mdbstore_nsm_store (dest->mdb_store, src->nsm);
+        if (UNLIKELY (rc < 0)) return LSUP_DB_ERR;
+    }
+
+    *dest_p = dest;
 
     return LSUP_OK;
 }
@@ -207,12 +205,6 @@ LSUP_graph_free (LSUP_Graph *gr)
 
         if (gr->store_type == LSUP_STORE_MEM)
             LSUP_htstore_free (gr->ht_store);
-        /*
-        // NOTE This is not required bacause MDB store is only one for now,
-        // cleared at exit.
-        else
-            LSUP_mdbstore_free (gr->mdb_store);
-        */
 
         free (gr);
     }
@@ -230,7 +222,13 @@ LSUP_graph_set_uri (LSUP_Graph *gr, const char *uri)
 
 LSUP_NSMap *
 LSUP_graph_namespace (const Graph *gr)
-{ return gr->nsm; }
+{
+    if (gr->store_type == LSUP_STORE_MEM) return gr->nsm;
+
+    LSUP_NSMap *nsm;
+    if (LSUP_mdbstore_nsm_get (gr->mdb_store, &nsm) < 0) return NULL;
+    return nsm;
+}
 
 
 void
@@ -286,22 +284,19 @@ LSUP_graph_add_init (LSUP_Graph *gr)
 LSUP_rc
 LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_SerTriple *sspo)
 {
-    if (it->graph->store_type == LSUP_STORE_MEM) {
+    if (it->graph->store_type == LSUP_STORE_MEM)
         return LSUP_htstore_add_iter (it->ht_iter, sspo);
-    } else {
-        return LSUP_mdbstore_add_iter (it->mdb_iter, sspo);
-    }
+
+    return LSUP_mdbstore_add_iter (it->mdb_iter, sspo);
 }
 
 
 void
 LSUP_graph_add_done (LSUP_GraphIterator *it)
 {
-    if (it->graph->store_type == LSUP_STORE_MEM) {
+    if (it->graph->store_type == LSUP_STORE_MEM)
         LSUP_htstore_add_done (it->ht_iter);
-    } else {
-        LSUP_mdbstore_add_done (it->mdb_iter);
-    }
+    else LSUP_mdbstore_add_done (it->mdb_iter);
 
     free (it);
     log_trace ("Done adding.");
@@ -420,24 +415,6 @@ LSUP_graph_lookup (const Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
 }
 
 
-/** @brief Advance iterator and return serialized triple.
- *
- * This is an internal function to pass raw buffers between higher-level
- * functions without serializing and deserializing triples.
- */
-inline static LSUP_rc
-graph_iter_next_buffer (GraphIterator *it, LSUP_SerTriple *sspo)
-{
-    LSUP_rc rc;
-
-    if (it->graph->store_type == LSUP_STORE_MEM)
-        rc = LSUP_htiter_next (it->ht_iter, sspo);
-    else rc = LSUP_mdbiter_next (it->mdb_iter, sspo);
-
-    return rc;
-}
-
-
 LSUP_rc
 LSUP_graph_iter_next (GraphIterator *it, LSUP_Triple *spo)
 {
@@ -508,5 +485,59 @@ LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
 }
 
 
-/* * * Static functions * * */
+/*
+ * Static functions.
+ */
+
+/** @brief Advance iterator and return serialized triple.
+ *
+ * This is an internal function to pass raw buffers between higher-level
+ * functions without serializing and deserializing triples.
+ */
+inline static LSUP_rc
+graph_iter_next_buffer (GraphIterator *it, LSUP_SerTriple *sspo)
+{
+    LSUP_rc rc;
+
+    if (it->graph->store_type == LSUP_STORE_MEM)
+        rc = LSUP_htiter_next (it->ht_iter, sspo);
+    else rc = LSUP_mdbiter_next (it->mdb_iter, sspo, NULL);
+
+    return rc;
+}
+
+
+/**
+ * Copy triples from a source graph into a destination one.
+ *
+ * The destination graph is not initialized here, so the copy is cumulative.
+ */
+static LSUP_rc
+graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest)
+{
+    LSUP_rc rc = LSUP_NOACTION;
+
+    GraphIterator *it = LSUP_graph_lookup (src, NULL, NULL, NULL, NULL);
+
+    LSUP_SerTriple sspo;
+
+    LSUP_GraphIterator *add_it = LSUP_graph_add_init (dest);
+    while (graph_iter_next_buffer (it, &sspo) != LSUP_END) {
+        LSUP_rc add_rc = LSUP_graph_add_iter (add_it, &sspo);
+
+        if (LIKELY (add_rc == LSUP_OK)) rc = LSUP_OK;
+        else if (add_rc < 0) return add_rc;
+    }
+    LSUP_graph_add_done (add_it);
+    LSUP_graph_iter_free (it);
+
+    return rc;
+}
+
+
+/**
+ * Extern inline definitions.
+ */
+
+size_t LSUP_graph_size (const LSUP_Graph *gr);
 

+ 5 - 5
src/store_htable.c

@@ -304,12 +304,12 @@ LSUP_htstore_add_iter (HTIterator *it, const LSUP_SerTriple *sspo)
     };
 
     // Add triple.
-    log_debug ("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
+    log_trace ("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
 
     TripleEntry *k_ins = NULL;
     HASH_FIND (hh, it->store->keys, spok, TRP_KLEN, k_ins);
     if (k_ins == NULL) {
-        log_debug ("Triple not found, inserting.");
+        log_trace ("Triple not found, inserting.");
         MALLOC_GUARD (k_ins, LSUP_MEM_ERR);
 
         memcpy (k_ins->key, spok, TRP_KLEN);
@@ -317,14 +317,14 @@ LSUP_htstore_add_iter (HTIterator *it, const LSUP_SerTriple *sspo)
 
         it->i++;
     } else {
-        log_debug ("Triple found. Skipping.");
+        log_trace ("Triple found. Skipping.");
         return LSUP_NOACTION;
     }
 
     // Add terms to index.
     for (int i = 0; i < 3; i++) {
         spok[i] = LSUP_buffer_hash (LSUP_striple_pos (sspo, i));
-        //log_debug ("Indexing term key %lx\n", spok[i]);
+        //log_trace ("Indexing term key %lx\n", spok[i]);
 
         IndexEntry *ins = NULL;
         HASH_FIND (hh, it->store->idx, spok + i, KLEN, ins);
@@ -473,7 +473,7 @@ LSUP_htiter_next (HTIterator *it, LSUP_SerTriple *sspo)
                 tkey_to_strp (it->store, it->entry->key, sspo);
                 if (!sspo->s || !sspo->p || !sspo->o) return LSUP_DB_ERR;
 
-                log_debug (
+                log_trace (
                     "Found spok: {%lx, %lx, %lx}",
                     it->entry->key[0], it->entry->key[1], it->entry->key[2]
                 );

+ 176 - 127
src/store_mdb.c

@@ -1,5 +1,7 @@
 #include <ftw.h>
 
+#include "uthash.h"
+
 #include "store_mdb.h"
 
 /**
@@ -23,8 +25,11 @@
 #define ENV_FILE_MODE 0640
 
 
-typedef char DbLabel[8];
+/*
+ * Data types.
+ */
 
+typedef char DbLabel[8];
 
 // TODO Most of these are no longer used. Clean up.
 typedef enum {
@@ -33,22 +38,19 @@ typedef enum {
     LSSTORE_DIRTY_TXN    = 4, // Main txn was opened in a subroutine.
 } StoreState;
 
-
 typedef enum {
     OP_ADD,
     OP_REMOVE,
 } StoreOp;
 
-
-typedef struct MDBStore {
-    MDB_env *           env;        // Environment handle.
-    MDB_txn *           txn;        // Current transaction.
-    MDB_dbi             dbi[N_DB];  // DB handles. Refer to DbIdx enum.
-    LSUP_Buffer *       default_ctx;// Default ctx as a serialized URI.
-    StoreState          state;      // Store state.
+typedef struct mdbstore_t {
+    MDB_env *           env;            // Environment handle.
+    MDB_txn *           txn;            // Current transaction.
+    MDB_dbi             dbi[N_DB];      // DB handles. Refer to DbIdx enum.
+    LSUP_Buffer *       default_ctx;    // Default ctx as a serialized URI.
+    StoreState          state;          // Store state.
 } MDBStore;
 
-
 /** @brief Iterator operation.
  *
  * Function executed for each iteration of a #MDBIterator. It assumes that a
@@ -59,15 +61,16 @@ typedef struct MDBStore {
  * value for the next result. It is up to the caller to evaluate this value
  * and decide whether to call the function again.
  */
-typedef void (*iter_op_fn_t)(struct MDBIterator *it);
+typedef void (*iter_op_fn_t)(LSUP_MDBIterator *it);
 
 
 /** @brief Triple iterator.
  */
-typedef struct MDBIterator {
+typedef struct mdbstore_iter_t {
     MDBStore *          store;      // MDB store pointer.
     MDB_txn *           txn;        // MDB transaction.
     MDB_cursor *        cur;        // MDB cursor.
+    MDB_cursor *        ctx_cur;    // MDB c:spo index cursor.
     MDB_val             key, data;  // Internal data handlers.
     LSUP_TripleKey      spok;       // Triple to be populated with match.
     LSUP_Key            ck;         // Ctx key to filter by. May be NULL_TRP.
@@ -76,10 +79,32 @@ typedef struct MDBIterator {
     LSUP_Key            luk[3];     // 0÷3 lookup keys.
     size_t              i;          // Internal counter for paged lookups.
     int                 rc;         // MDB_* return code for the next result.
-    StoreState          state;      // State flags.
 } MDBIterator;
 
 
+// Set of single keys.
+typedef struct key_set_t {
+    LSUP_Key            key;
+    UT_hash_handle      hh;
+} KeySet;
+
+// Set of triple keys.
+typedef struct triple_set_t {
+    LSUP_TripleKey      spok;
+    UT_hash_handle      hh;
+} TripleSet;
+
+// Map of context to triple set.
+typedef struct ctx_triple_map_t {
+    LSUP_Key            ck;
+    TripleSet *         spok;
+    UT_hash_handle      hh;
+} CtxTripleMap;
+
+/*
+ * Static variables.
+ */
+
 /*
  * TODO At the moment up to 64-bit key / hash values are allowed. Later on,
  * 128-bit keys should be allowed by compile options, and that will no longer
@@ -121,7 +146,7 @@ MAIN_TABLE
 LOOKUP_TABLE
 #undef ENTRY
 
-/**
+/*
  * Numeric index of each DB. Prefixed with IDX_
  *
  * These index numbers are referred to in all the arrays defeined below. They
@@ -144,7 +169,7 @@ static const char *db_labels[N_DB] = {
 #undef ENTRY
 };
 
-/**
+/*
  * DB flags. These are aligned with the dbi_labels index.
  */
 static const unsigned int db_flags[N_DB] = {
@@ -154,7 +179,7 @@ static const unsigned int db_flags[N_DB] = {
 #undef ENTRY
 };
 
-/**
+/*
  * 1-bound and 2-bound lookup indices.
  *
  * N.B. Only the first 6 (1-bound and 2-bound term lookup) are used.
@@ -179,7 +204,7 @@ static const uint8_t lookup_ordering_2bound[3][3] = {
 };
 
 
-/**
+/*
  * Static prototypes.
  */
 static int index_triple(
@@ -382,12 +407,6 @@ LSUP_mdbstore_add_iter (MDBIterator *it, const LSUP_SerTriple *sspo)
     for (int i = 0; i < 3; i++) {
         LSUP_Buffer *st = LSUP_striple_pos (sspo, i);
 
-#ifdef DEBUG3
-        printf ("Inserting term: ");
-        LSUP_buffer_print (st);
-        printf ("\n");
-#endif
-
         spok[i] = LSUP_buffer_hash (st);
 
         it->key.mv_data = spok + i;
@@ -405,8 +424,8 @@ LSUP_mdbstore_add_iter (MDBIterator *it, const LSUP_SerTriple *sspo)
         }
     }
 
-    log_debug ("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
-    log_debug ("Into context: %lx", it->ck);
+    log_trace ("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
+    log_trace ("Into context: %lx", it->ck);
 
     // Insert spo:c.
     it->key.mv_data = spok;
@@ -485,37 +504,31 @@ LSUP_mdbstore_add (
 }
 
 
-static LSUP_Key __attribute__ ((unused))
+/*
+static LSUP_Key
 sterm_to_key (
         LSUP_MDBStore *store, const LSUP_Buffer *sterm)
 {
     // TODO this will be replaced by a lookup when 128-bit hash is introduced.
     return LSUP_buffer_hash (sterm);
 }
+*/
 
 
 static LSUP_rc
-key_to_sterm(
-        LSUP_MDBStore *store, const LSUP_Key key, LSUP_Buffer *sterm,
-        MDB_txn *txn)
+key_to_sterm (LSUP_MDBIterator *it, const LSUP_Key key, LSUP_Buffer *sterm)
 {
     LSUP_rc rc = LSUP_NORESULT;
     int db_rc;
-    bool txn_dirty = false;
-
-    if (!txn) {
-        db_rc = mdb_txn_begin (store->env, NULL, MDB_RDONLY, &txn);
-        if (db_rc != MDB_SUCCESS) return LSUP_DB_ERR;
-        txn_dirty = true;
-    }
 
     MDB_val key_v, data_v;
     key_v.mv_data = (void*)&key;
     key_v.mv_size = KLEN;
 
-    db_rc = mdb_get (txn, store->dbi[IDX_T_ST], &key_v, &data_v);
+    db_rc = mdb_get (it->txn, it->store->dbi[IDX_T_ST], &key_v, &data_v);
 
     if (db_rc == MDB_SUCCESS) {
+        free (sterm->addr);
         sterm->addr = data_v.mv_data;
         sterm->size = data_v.mv_size;
         rc = LSUP_OK;
@@ -525,8 +538,6 @@ key_to_sterm(
         sterm->size = 0;
     } else rc = LSUP_ERROR;
 
-    if (txn_dirty) mdb_txn_abort (txn);
-
     return rc;
 }
 
@@ -553,6 +564,24 @@ LSUP_mdbstore_lookup(
 
     uint8_t idx0, idx1;
 
+    // Start RO transaction if not in a write txn already.
+    if (it->store->txn) it->txn = it->store->txn;
+    else {
+        it->rc = mdb_txn_begin (it->store->env, NULL, MDB_RDONLY, &it->txn);
+        if (it->rc != MDB_SUCCESS) {
+            log_error ("Database error: %s", LSUP_strerror (it->rc));
+            return NULL;
+        }
+    }
+
+    // Context index loop.
+    if (UNLIKELY (mdb_cursor_open (
+            it->txn, it->store->dbi[IDX_SPO_C], &it->ctx_cur) != MDB_SUCCESS))
+        return NULL;
+
+    /*
+     * Lookup decision tree.
+     */
     // s p o (all terms bound)
     if (spok[0] != NULL_KEY && spok[1] != NULL_KEY && spok[2] != NULL_KEY) {
         it->luk[0] = spok[0];
@@ -605,8 +634,13 @@ LSUP_mdbstore_lookup(
 }
 
 
+/** @brief Get next iterator key.
+ *
+ * The ck pointer is filled with an array of contexts that the triple appears
+ * in, if not NULL.
+ */
 inline static LSUP_rc
-mdbiter_next_key (LSUP_MDBIterator *it)
+mdbiter_next_key (LSUP_MDBIterator *it, KeySet **ck_p)
 {
     if (UNLIKELY (!it)) return LSUP_VALUE_ERR;
 
@@ -628,22 +662,15 @@ mdbiter_next_key (LSUP_MDBIterator *it)
             "Found  spok: {%lx, %lx, %lx}",
             it->spok[0], it->spok[1], it->spok[2]);
 
-    if (it->ck) {
-        rc = LSUP_NORESULT;  // Intermediary value, will never be returned.
+    MDB_val key, data;
+    int db_rc;
 
-        MDB_cursor *cur;
-        MDB_val key, data;
+    key.mv_size = TRP_KLEN;
+    data.mv_data = &it->ck;
+    data.mv_size = KLEN;
 
-        int db_rc;
-        db_rc = mdb_cursor_open (it->txn, it->store->dbi[IDX_SPO_C], &cur);
-        if (UNLIKELY (db_rc != MDB_SUCCESS)) {
-            log_error ("Database error: %s", LSUP_strerror (db_rc));
-            return LSUP_DB_ERR;
-        }
-
-        key.mv_size = TRP_KLEN;
-        data.mv_data = &it->ck;
-        data.mv_size = KLEN;
+    if (it->ck) {
+        rc = LSUP_NORESULT;  // Intermediary value, will never be returned.
 
         while (rc == LSUP_NORESULT) {
             //log_debug ("begin ctx loop.");
@@ -654,14 +681,13 @@ mdbiter_next_key (LSUP_MDBIterator *it)
             // is an error (LSUP_DB_ERR).
             key.mv_data = it->spok;
 
-            db_rc = mdb_cursor_get (cur, &key, &data, MDB_GET_BOTH);
+            db_rc = mdb_cursor_get (it->ctx_cur, &key, &data, MDB_GET_BOTH);
 
             if (db_rc == MDB_SUCCESS) {
                 rc = LSUP_OK;
                 log_trace ("Triple found for context.");
-            }
 
-            else if (db_rc == MDB_NOTFOUND) {
+            } else if (db_rc == MDB_NOTFOUND) {
                 log_trace ("No triples found for context.");
                 if (it->rc == MDB_NOTFOUND) rc = LSUP_END;
                 else it->iter_op_fn (it);
@@ -673,25 +699,60 @@ mdbiter_next_key (LSUP_MDBIterator *it)
 
         }
 
-        mdb_cursor_close (cur);
-
     } else rc = LSUP_OK;
 
+    // Get all contexts for a triple if requested. Add up to previous
+    // iterations if the same pointer is passed.
+    if (ck_p) {
+        key.mv_data = it->spok;
+        db_rc = mdb_cursor_get (it->ctx_cur, &key, &data, MDB_SET_KEY);
+        if (db_rc == MDB_SUCCESS) {
+            do {
+                KeySet *entry;
+                HASH_FIND (hh, *ck_p, data.mv_data, KLEN, entry);
+                if (!entry) {
+                    MALLOC_GUARD (entry, LSUP_MEM_ERR);
+                    entry->key = *(LSUP_Key *) data.mv_data;
+                    HASH_ADD (hh, *ck_p, key, KLEN, entry);
+                }
+            } while (
+                    mdb_cursor_get (it->ctx_cur, &key, &data, MDB_NEXT_DUP)
+                    == MDB_SUCCESS);
+        }
+    }
+
     return rc;
 }
 
 
 LSUP_rc
-LSUP_mdbiter_next (LSUP_MDBIterator *it, LSUP_SerTriple *sspo)
+LSUP_mdbiter_next (
+        LSUP_MDBIterator *it, LSUP_SerTriple *sspo, LSUP_Buffer **ctx_p)
 {
-    LSUP_rc rc = mdbiter_next_key (it);
+    LSUP_rc rc;
+    KeySet *ck = NULL;
+
+    rc = (ctx_p) ? mdbiter_next_key (it, &ck) : mdbiter_next_key (it, NULL);
+
+    if (rc == LSUP_OK) {
+        if (sspo) {
+            key_to_sterm (it, it->spok[0], sspo->s);
+            key_to_sterm (it, it->spok[1], sspo->p);
+            key_to_sterm (it, it->spok[2], sspo->o);
 
-    if (sspo && rc == LSUP_OK) {
-        key_to_sterm (it->store, it->spok[0], sspo->s, it->txn);
-        key_to_sterm (it->store, it->spok[1], sspo->p, it->txn);
-        key_to_sterm (it->store, it->spok[2], sspo->o, it->txn);
+            // TODO error handling.
+        }
 
-        // TODO error handling.
+        // One-shot contexts for current triple.
+        if (ctx_p) {
+            KeySet *ccur;
+            LSUP_Buffer *ctx = malloc (HASH_COUNT (ck) * sizeof (*ctx));
+            size_t i = 0;
+            for (ccur = ck; ccur != NULL; ccur = ccur->hh.next)
+                key_to_sterm (it, ccur->key, ctx + (i++));
+
+            // TODO error handling.
+        }
     }
 
     return rc;
@@ -708,6 +769,7 @@ LSUP_mdbiter_free (MDBIterator *it)
 {
     if (it) {
         if (it->cur) mdb_cursor_close (it->cur);
+        if (it->ctx_cur) mdb_cursor_close (it->ctx_cur);
         if (it->store->txn != it->txn) mdb_txn_abort (it->txn);
 
         free (it);
@@ -747,7 +809,7 @@ LSUP_mdbstore_remove(
     if (UNLIKELY (!it)) return LSUP_DB_ERR;
     if (ct) log_debug ("Found %lu triples to remove.", *ct);
 
-    while (mdbiter_next_key (it) == LSUP_OK) {
+    while (mdbiter_next_key (it, NULL) == LSUP_OK) {
         spok_v.mv_data = it->spok;
 
         rc = mdb_cursor_get (dcur, &spok_v, &ck_v, MDB_GET_BOTH);
@@ -803,6 +865,35 @@ _remove_abort:
 }
 
 
+LSUP_Buffer **
+LSUP_mdbstore_lookup_contexts (
+        LSUP_MDBStore *store, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
+        const LSUP_Buffer *so)
+{
+    LSUP_MDBIterator *it = LSUP_mdbstore_lookup (
+            store, ss, sp, so, NULL, NULL);
+
+    LSUP_rc rc = LSUP_NORESULT;
+    KeySet *ckey, *ckeys = NULL, *tmp;
+    while (rc != LSUP_END)
+        rc = mdbiter_next_key (it, &ckeys);
+
+    size_t i = 0;
+    LSUP_Buffer **ctx_a = calloc (HASH_COUNT (ckeys) + 1, sizeof (*ctx_a));
+    if (UNLIKELY (!ctx_a)) return NULL;
+
+    HASH_ITER (hh, ckeys, ckey, tmp) {
+        ctx_a[i] = BUF_DUMMY;
+        key_to_sterm (it, ckey->key, ctx_a[i++]);
+        HASH_DEL (ckeys, ckey);
+        free (ckey);
+    }
+    LSUP_mdbiter_free (it);
+
+    return ctx_a;
+}
+
+
 LSUP_rc
 LSUP_mdbstore_nsm_get (LSUP_MDBStore *store, LSUP_NSMap **nsm_p)
 {
@@ -848,16 +939,20 @@ finally:
 LSUP_rc
 LSUP_mdbstore_nsm_store (LSUP_MDBStore *store, const LSUP_NSMap *nsm)
 {
+    MDB_txn *txn;
+    if (!store->txn) {
+        RCCK (mdb_txn_begin (store->env, NULL, 0, &txn));
+    }
+    else txn = store->txn;
+
     LSUP_rc rc = LSUP_NOACTION;
     int db_rc;
 
     MDB_cursor *dcur = NULL, *icur = NULL;
     if (
-        mdb_cursor_open (
-                store->txn, store->dbi[IDX_PFX_NS], &dcur) != MDB_SUCCESS
+        mdb_cursor_open (txn, store->dbi[IDX_PFX_NS], &dcur) != MDB_SUCCESS
         ||
-        mdb_cursor_open (
-                store->txn, store->dbi[IDX_NS_PFX], &icur) != MDB_SUCCESS
+        mdb_cursor_open (txn, store->dbi[IDX_NS_PFX], &icur) != MDB_SUCCESS
     ) {
         rc = LSUP_DB_ERR;
         goto finally;
@@ -896,6 +991,8 @@ LSUP_mdbstore_nsm_store (LSUP_MDBStore *store, const LSUP_NSMap *nsm)
 finally:
     if (icur) mdb_cursor_close (icur);
     if (dcur) mdb_cursor_close (dcur);
+    free (nsm_data);
+    if (txn != store->txn) mdb_txn_commit (txn);
 
     return rc;
 }
@@ -1128,15 +1225,6 @@ lookup_0bound (MDBIterator *it, size_t *ct)
 {
     log_debug ("Looking up 0 bound terms.");
 
-    if (it->store->txn) it->txn = it->store->txn;
-    else {
-        it->rc = mdb_txn_begin (it->store->env, NULL, MDB_RDONLY, &it->txn);
-        if (it->rc != MDB_SUCCESS) {
-            log_error ("Database error: %s", LSUP_strerror (it->rc));
-            return LSUP_DB_ERR;
-        }
-    }
-
     if (ct) {
         if (it->ck != NULL_KEY) {
             // Look up by given context.
@@ -1192,18 +1280,6 @@ lookup_1bound (uint8_t idx0, MDBIterator *it, size_t *ct)
 
     log_debug ("Looking up 1 bound term: %lx", it->luk[0]);
 
-    if (!it->txn) {
-        if (it->store->txn) it->txn = it->store->txn;
-        else {
-            it->rc = mdb_txn_begin (
-                    it->store->env, NULL, MDB_RDONLY, &it->txn);
-            if (it->rc != MDB_SUCCESS) {
-                log_error ("Database error: %s", LSUP_strerror (it->rc));
-                return LSUP_DB_ERR;
-            }
-        }
-    }
-
     mdb_cursor_open (it->txn, it->store->dbi[lookup_indices[idx0]], &it->cur);
 
     it->key.mv_data = it->luk;
@@ -1214,27 +1290,24 @@ lookup_1bound (uint8_t idx0, MDBIterator *it, size_t *ct)
         // the context is to loop over them.
         if (it->ck != NULL_KEY) {
             log_debug ("Counting in context: %lx", it->ck);
-            MDBIterator *ct_it;
+            MDBIterator *ct_it = NULL;
             MALLOC_GUARD (ct_it, LSUP_MEM_ERR);
 
             ct_it->luk[0] = it->luk[0];
-            /*
-            LSUP_TripleKey ct_spok;
-            memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
-            */
             ct_it->ck = it->ck;
             ct_it->store = it->store;
             ct_it->txn = it->txn;
             ct_it->key = it->key;
             ct_it->data = it->data;
             ct_it->i = 0;
+            ct_it->ctx_cur = it->ctx_cur;
 
             LSUP_rc rc = lookup_1bound (idx0, ct_it, NULL);
             if (rc < 0) return rc;
 
-            while (LSUP_mdbiter_next (ct_it, NULL) != LSUP_END) {
+            while (LSUP_mdbiter_next (ct_it, NULL, NULL) != LSUP_END) {
                 (*ct)++;
-                log_debug ("Counter increased to %lu.", *ct);
+                log_trace ("Counter increased to %lu.", *ct);
             }
 
             // Free the counter iterator without freeing the shared txn.
@@ -1289,7 +1362,7 @@ lookup_2bound(uint8_t idx0, uint8_t idx1, MDBIterator *it, size_t *ct)
                 luk2_offset = 0;
             }
             dbi = it->store->dbi[lookup_indices[i + 3]];
-            log_debug(
+            log_debug (
                     "Looking up 2 bound in %s",
                     db_labels[lookup_indices[i + 3]]);
 
@@ -1298,7 +1371,7 @@ lookup_2bound(uint8_t idx0, uint8_t idx1, MDBIterator *it, size_t *ct)
     }
 
     if (dbi == 0) {
-        log_debug(
+        log_error (
                 "Values %d and %d not found in lookup keys.",
                 idx0, idx1);
         return LSUP_VALUE_ERR;
@@ -1309,18 +1382,6 @@ lookup_2bound(uint8_t idx0, uint8_t idx1, MDBIterator *it, size_t *ct)
     luk[luk1_offset] = it->luk[0];
     luk[luk2_offset] = it->luk[1];
 
-    if (!it->txn) {
-        if (it->store->txn) it->txn = it->store->txn;
-        else {
-            it->rc = mdb_txn_begin (
-                    it->store->env, NULL, MDB_RDONLY, &it->txn);
-            if (it->rc != MDB_SUCCESS) {
-                log_error ("Database error: %s", LSUP_strerror (it->rc));
-                return LSUP_DB_ERR;
-            }
-        }
-    }
-
     it->key.mv_data = luk;
     it->key.mv_size = DBL_KLEN;
 
@@ -1336,16 +1397,13 @@ lookup_2bound(uint8_t idx0, uint8_t idx1, MDBIterator *it, size_t *ct)
 
             ct_it->luk[0] = it->luk[0];
             ct_it->luk[1] = it->luk[1];
-            /*
-            LSUP_TripleKey ct_spok;
-            memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
-            */
             ct_it->ck = it->ck;
             ct_it->store = it->store;
             ct_it->txn = it->txn;
+            ct_it->ctx_cur = it->ctx_cur;
             lookup_2bound (idx0, idx1, ct_it, NULL);
 
-            while (LSUP_mdbiter_next (ct_it, NULL) != LSUP_END) {
+            while (LSUP_mdbiter_next (ct_it, NULL, NULL) != LSUP_END) {
                 ct[0] ++;
             }
 
@@ -1382,15 +1440,6 @@ lookup_3bound (MDBIterator *it, size_t *ct)
             "Looking up 3 bound: {%lx, %lx, %lx}",
             it->luk[0], it->luk[1], it->luk[2]);
 
-    if (it->store->txn) it->txn = it->store->txn;
-    else {
-        it->rc = mdb_txn_begin (it->store->env, NULL, MDB_RDONLY, &it->txn);
-        if (it->rc != MDB_SUCCESS) {
-            log_error ("Database error: %s", LSUP_strerror (it->rc));
-            return LSUP_DB_ERR;
-        }
-    }
-
     it->key.mv_data = it->luk;
 
     if (it->ck != NULL_KEY) {

+ 7 - 6
test.c

@@ -23,17 +23,18 @@ int main(int argc, char **argv) {
         codec_nt_tests() ||
         0
     ) {
-        printf("Test failed.");
+        log_error ("Test failed.");
         rc = -1;
     } else {
-        printf(
-              "\n****************\n"
-                "ALL TESTS PASSED\n"
-                "****************\n");
+        log_info ("");
+        log_info ("********************");
+        log_info ("* ALL TESTS PASSED *");
+        log_info ("********************");
         rc = 0;
     }
 
-    printf("\nTests run: %d\n", tests_run);
+    log_info ("");
+    log_info ("Tests run: %d", tests_run);
 
     return rc;
 }

+ 2 - 2
test/test.h

@@ -50,8 +50,8 @@
     int rc = (exp); \
     if (rc != LSUP_OK) {\
         fprintf(\
-                stderr, "!!! Test failed at %s:%d. Error code: %d\n",\
-                __FILE__, __LINE__, rc); \
+                stderr, "!!! Test failed at %s:%d. Error: %s (%d)\n",\
+                __FILE__, __LINE__, LSUP_strerror(rc), rc); \
         return -1; \
     }\
 } while (0)

+ 1 - 2
test/test_codec_nt.c

@@ -222,9 +222,8 @@ test_decode_nt_graph()
     EXPECT_INT_EQ (LSUP_graph_size (gr), 7);
 
     for (int i = 0; i < 7; i++) {
-        log_info ("Checking triple #%d... ", i);
+        log_info ("Checking triple #%d.", i);
         EXPECT_INT_EQ (LSUP_graph_contains (gr, trp + i), 1);
-        printf("OK.\n");
     }
 
     LSUP_graph_free (gr);

+ 47 - 4
test/test_graph.c

@@ -40,9 +40,8 @@ _graph_add (LSUP_store_type type)
     EXPECT_INT_EQ (LSUP_graph_size (gr), 8);
 
     for (int i = 0; i < sizeof (trp); i++) {
-        log_info ("checking triple #%d... ", i);
+        log_info ("checking triple #%d.", i);
         ASSERT (LSUP_graph_contains (gr, trp + i), "Triple not in graph!");
-        printf ("OK.\n");
     }
 
     LSUP_Triple *missing_trp = LSUP_triple_new (trp[1].s, trp[6].p, trp[4].o);
@@ -88,6 +87,7 @@ _graph_lookup (LSUP_store_type type)
         0, 0, 0
     };
 
+    /* TODO
     // Index of lookup matches from trp.
     size_t lu_match[N_LUT][8] = {
         {0, 1, 2, 3, 4, 5, 6, 7},
@@ -105,6 +105,7 @@ _graph_lookup (LSUP_store_type type)
         {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7},
         {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7},
     };
+    */
 
     LSUP_Graph *gr = LSUP_graph_new (type);
 
@@ -115,12 +116,11 @@ _graph_lookup (LSUP_store_type type)
     EXPECT_INT_EQ (LSUP_graph_size (gr), 8);
 
     for (int i = 0; i < N_LUT; i++) {
-        log_info ("Checking triple #%d on %d... ", i, type);
+        log_info ("Checking triple #%d on %d.", i, type);
         LSUP_GraphIterator *it = LSUP_graph_lookup (
                 gr, lu_trp[i][0], lu_trp[i][1], lu_trp[i][2], &ct);
         if (type != LSUP_STORE_MEM) // TODO not implemented in htable.
             EXPECT_INT_EQ (ct, lu_ct[i]);
-        printf ("done.\n");
 
         /* TODO
         for (int j = 0; j < 8; j++) {
@@ -213,12 +213,55 @@ static int test_graph_remove() {
 }
 
 
+static int test_graph_copy()
+{
+    LSUP_Triple *trp = create_triples();
+
+    LSUP_Graph *gr1 = LSUP_graph_new (LSUP_STORE_MEM);
+    ASSERT (gr1 != NULL, "Error creating graph!");
+
+    LSUP_graph_add_trp (gr1, trp, NULL);
+
+    LSUP_Graph *gr2 = LSUP_graph_copy (gr1);
+    EXPECT_INT_EQ (LSUP_graph_size (gr2), 8);
+
+    for (int i = 0; i < sizeof (trp); i++) {
+        log_info ("checking triple #%d.", i);
+        ASSERT (
+                LSUP_graph_contains (gr2, trp + i),
+                "Triple not in copied graph!");
+    }
+
+    LSUP_Graph *gr3;
+    EXPECT_PASS (LSUP_graph_store (gr1, &gr3, NULL));
+
+    ASSERT (
+            LSUP_term_equals (LSUP_graph_uri (gr1), LSUP_graph_uri (gr3)),
+            "Stored graph has different URI than source!");
+
+    for (int i = 0; i < sizeof (trp); i++) {
+        log_info ("checking triple #%d.", i);
+        ASSERT (
+                LSUP_graph_contains (gr3, trp + i),
+                "Triple not in stored graph!");
+    }
+
+    LSUP_graph_free (gr3);
+    LSUP_graph_free (gr2);
+    LSUP_graph_free (gr1);
+    free_triples (trp);
+
+    return 0;
+}
+
+
 int graph_tests()
 {
     RUN (test_graph_new);
     RUN (test_graph_add);
     RUN (test_graph_lookup);
     RUN (test_graph_remove);
+    RUN (test_graph_copy);
 
     return 0;
 }

+ 24 - 22
test/test_store_mdb.c

@@ -79,6 +79,7 @@ static int test_triple_store()
 
         LSUP_MDBIterator *it = LSUP_mdbstore_lookup(
                 store, lut[i][0], lut[i][1], lut[i][2], luc[i], &ct);
+        ASSERT (it != NULL, "Error creating iterator!");
         EXPECT_INT_EQ (ct, results[i]);
 
         LSUP_mdbiter_free (it);
@@ -256,39 +257,40 @@ static int test_quad_store()
         0, 0,                   // #40
     };
 
+    int ctx_ct[10] = {
+        2,
+        2, 1, 0, 1,
+        2, 1, 2,
+        2, 0,
+    };
+
     for (int i = 0; i < 41; i++) {
         size_t ct;
 
-        /*
-        printf ("Testing triple lookup #%d: {", i);
-
-        if (lut[i].s) LSUP_buffer_print (lut[i].s);
-        else printf ("NULL");
-        printf (", ");
-
-        if (lut[i].p) LSUP_buffer_print (lut[i].p);
-        else printf ("NULL");
-        printf (", ");
-
-        if (lut[i].o) LSUP_buffer_print (lut[i].o);
-        else printf ("NULL");
-        printf (", ");
-
-        if (luc[i]) LSUP_buffer_print (luc[i]);
-        else printf ("NULL");
-        printf ("}\n");
-        */
-
-        log_info ("Checking triple #%d...", i);
+        log_info ("Checking triple #%d.", i);
         LSUP_MDBIterator *it = LSUP_mdbstore_lookup(
                 store, lut[i][0], lut[i][1], lut[i][2], luc[i], &ct);
         ASSERT (it != NULL, "Lookup error!");
         EXPECT_INT_EQ (ct, results[i]);
-        printf ("OK.\n");
 
         LSUP_mdbiter_free (it);
     }
 
+    // Check triple contexts.
+    for (int i = 0; i < 10; i++) {
+        log_info ("Checking contexts for triple %d.", i);
+        LSUP_Buffer **ctx_a = LSUP_mdbstore_lookup_contexts (
+                store, lut[i][0], lut[i][1], lut[i][2]);
+        ASSERT (ctx_a != NULL, "Value is NULL!");
+
+        int j = 0;
+        while (ctx_a[j] != NULL)
+            free (ctx_a[j++]); // Buffer data are memory-mapped. Not freeing.
+        free (ctx_a);
+
+        EXPECT_INT_EQ (j, ctx_ct[i]);
+    }
+
     for (int i = 0; i < NUM_TRP; i++) {
         LSUP_buffer_free (ser_trp[i].s);
         LSUP_buffer_free (ser_trp[i].p);