Browse Source

Change underlying context when changing graph URI.

scossu 13 hours ago
parent
commit
48ba95059d
8 changed files with 378 additions and 425 deletions
  1. 5 4
      include/graph.h
  2. 40 5
      include/store_interface.h
  3. 35 7
      src/graph.c
  4. 4 15
      src/store.c
  5. 153 44
      src/store_mdb.c
  6. 2 0
      test.c
  7. 74 37
      test/test_graph.c
  8. 65 313
      test/test_store_mdb.c

+ 5 - 4
include/graph.h

@@ -183,10 +183,11 @@ LSUP_graph_store (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.
+ * (i.e. LSUP_STORE_MDB*) effectively changes the underlying context that the
+ * triples are bound 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 a graph URI changes, and relative URI lookups are resolved in
+ * real-time against the current graph URI.
  *
  * @param gr[in] Graph handle.
  *

+ 40 - 5
include/store_interface.h

@@ -69,7 +69,7 @@ typedef enum {
  * e.g. a SQL connection string, file path for an embedded store, the URL of a
  * REST API endpoint, etc. It may also be NULL, in which case it will be set to
  * the default identifier set by the implementation. It can be retrieved from
- * an existing store via #store_id_fn_t .
+ * an existing store via #store_id_fn_t() .
  *
  * @param[in] clear Whether to remove an existing environment with the same ID.
  */
@@ -170,6 +170,29 @@ typedef LSUP_rc (*store_txn_commit_fn_t)(void *store);
 typedef void (*store_txn_abort_fn_t)(void *store);
 
 
+/** @brief Update the context of triples in a context-aware store.
+ *
+ * When a context URI is updated, all relationships of triples to that context
+ * must be updated to reflect the new context.
+ *
+ * Only defined in stores with the LSUP_STORE_CTX feature.
+ *
+ * @param[in] store Store handle.
+ *
+ * @param[in] old_c Old context handle.
+ *
+ * @param[in] new_c New context handle.
+ *
+ * @param[in] udata Implementation-specific context data.
+ *
+ * @return LSUP_OK on success; LSUP_NOACTION if no triples were found under the
+ *  given context; <0 on error.
+ */
+typedef LSUP_rc (*store_update_ctx_fn_t)(
+        void *store, const LSUP_Buffer *old_c, const LSUP_Buffer *new_c,
+        void *udata);
+
+
 /** @brief Initialize bulk triple load.
  *
  * This is the first step of a bulk load. It is best used when the data at hand
@@ -255,6 +278,9 @@ typedef LSUP_rc (*store_add_done_fn_t)(void *it);
  * @param[in] store Store handle.
  *
  * @param[in] sterm Serialized term to store.
+ *
+ * @param[in] udata Implementation-defined data. See individual
+ *  implementations' documentation.
  */
 typedef LSUP_rc (*store_add_term_fn_t)(
         void *store, const LSUP_Buffer *sterm, void *udata);
@@ -294,7 +320,7 @@ typedef LSUP_rc (*store_add_term_fn_t)(
  *
  * @return Iterator 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
+ * #iter_free_fn_t() after use. If matches are found, the iterator points to
  * the first result which can be retrieved with #iter_next_fn().
  */
 typedef void * (*store_lookup_fn_t)(
@@ -321,8 +347,8 @@ typedef bool (*store_trp_exist_fn_t)(
 /** @brief Prototype: delete triples by pattern matching.
  *
  * The ss, sp, so, sc terms act as a matching pattern as documented in
- * @sa #store_lookup_fn. if not NULL, ct yields the number of triples actually
- * deleted.
+ * @sa #store_lookup_fn(). if not NULL, ct yields the number of triples
+ * actually deleted.
  *
  *  @param[in] udata User data. Consult individual store implementations for
  *   how this is interpreted.
@@ -386,7 +412,7 @@ typedef LSUP_NSMap * (*store_nsm_get_fn_t)(void *store);
  * without the LSUP_STORE_COW, data are copied on retrieval and the resulting
  * buffers can be freed with #LSUP_buffer_free() or analogous methods.
  *
- * @param[in] it Opaque iterator handle obtained with the store's #lookup_fn.
+ * @param[in] it Opaque iterator handle obtained with the store's #lookup_fn().
  *
  * @param[out] sspo #LSUP_BufferTriple to be populated with three serialized
  * terms if found. It may be NULL, in which case it is not populated.
@@ -455,6 +481,13 @@ typedef struct store_if_t {
     store_txn_abort_fn_t txn_abort_fn;  ///< Abort transaction.
     iter_txn_fn_t       iter_txn_fn;    ///< Get iterator's transaction.
 
+    //Context setting.
+    store_update_ctx_fn_t update_ctx_fn; /**< Update context URI.
+                                         *
+                                         *   Only available in stores with
+                                         *   #LSUP_STORE_CTX feature. Optional.
+                                         */
+
     // Addition.
     store_add_init_fn_t add_init_fn;    ///< Initialize add iteration.
     store_add_iter_fn_t add_iter_fn;    ///< Add one triple.
@@ -516,6 +549,8 @@ const LSUP_StoreInt my_store_int = {
     .txn_commit_fn  = my_txn_commit_fn,
     .txn_abort_fn   = my_txn_abort_fn,
 
+    .ctx_update_fn  = my_ctx_update_fn,
+
     .add_init_fn    = my_init_fn,
     .add_iter_fn    = my_iter_fn,
     .add_abort_fn   = my_add_abort_fn,

+ 35 - 7
src/graph.c

@@ -250,10 +250,33 @@ LSUP_graph_set_uri (LSUP_Graph *gr, LSUP_Term *uri)
         return LSUP_VALUE_ERR;
     }
 
+    // Update context for triples in the graph.
+    LSUP_rc rc = LSUP_OK;
+    LSUP_Buffer *old_sc = NULL, *new_sc = NULL;
+
+    if (gr->store->sif->features & LSUP_STORE_CTX) {
+        old_sc = LSUP_term_serialize (gr->uri);
+        new_sc = LSUP_term_serialize (uri);
+        if (UNLIKELY (!old_sc || !new_sc)) {
+            rc = LSUP_MEM_ERR;
+            goto finally;
+        }
+
+        PCHECK (rc = gr->store->sif->update_ctx_fn (
+                    gr->store->data, old_sc, new_sc, NULL), finally);
+
+        // Overall success even if rc of underlying fn was LSUP_NOACTION.
+        if (rc == LSUP_NOACTION) rc = LSUP_OK;
+    }
+
     LSUP_term_free (gr->uri);
     gr->uri = uri;
 
-    return LSUP_OK;
+finally:
+    if (old_sc) LSUP_buffer_free (old_sc);
+    if (new_sc) LSUP_buffer_free (new_sc);
+
+    return rc;
 }
 
 
@@ -339,7 +362,7 @@ LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo)
 
     LSUP_Triple *rel_spo = LSUP_triple_new (rel_s, spo->p, rel_o);
     LOG_TRACE (
-            "Relative triple: {%s %s %s}",
+            "Adding relative triple: {%s, %s, %s}",
             rel_s->data, spo->p->data, rel_o->data);
 
     // Serialize relative triple.
@@ -363,8 +386,11 @@ LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo)
             LSUP_Term *term = LSUP_triple_pos (spo, i);
             if (term->type == LSUP_TERM_LITERAL) {
                 LSUP_Buffer *ser_dtype = LSUP_term_serialize (term->datatype);
+                void *txn =
+                        sif->features & LSUP_STORE_TXN ?
+                        sif->iter_txn_fn (it->data) : NULL;
                 LSUP_rc term_rc = sif->add_term_fn (
-                        it->graph->store->data, ser_dtype, it->data);
+                        it->graph->store->data, ser_dtype, txn);
                 PCHECK (term_rc, finally);
                 LSUP_buffer_free (ser_dtype);
             }
@@ -484,12 +510,14 @@ LSUP_graph_lookup_txn (
 
     // Make relative s and o.
     LSUP_Term *rel_s, *rel_o;
-    if (s && LSUP_IS_IRI (s))
+    if (s && LSUP_IS_IRI (s)) {
         rel_s = LSUP_iriref_relative (gr->uri, s);
-    else rel_s = (LSUP_Term *)s;
-    if (o && LSUP_IS_IRI (o))
+        LOG_DEBUG ("Relative S lookup: %s", rel_s->data);
+    } else rel_s = (LSUP_Term *)s;
+    if (o && LSUP_IS_IRI (o)) {
         rel_o = LSUP_iriref_relative (gr->uri, o);
-    else rel_o = (LSUP_Term *)o;
+        LOG_DEBUG ("Relative O lookup: %s", rel_o->data);
+    } else rel_o = (LSUP_Term *)o;
 
     LSUP_Buffer
         *ss = LSUP_term_serialize (rel_s),

+ 4 - 15
src/store.c

@@ -2,26 +2,14 @@
 
 
 #define ENTRY(a, b) case LSUP_STORE_##a: return &b;
-static inline const LSUP_StoreInt *
-select_interface (LSUP_StoreType be) {
-    switch (be) {
-        BACKEND_TBL
-        default: return NULL;
-    }
-}
-#undef ENTRY
-
-
 const LSUP_StoreInt *
 LSUP_store_int (LSUP_StoreType type) {
     switch (type) {
-#define ENTRY(a, b) \
-        case LSUP_STORE_##a: return &b;
-BACKEND_TBL
-#undef ENTRY
+        BACKEND_TBL
         default: return NULL;
     }
 }
+#undef ENTRY
 
 
 LSUP_Store *
@@ -33,7 +21,7 @@ LSUP_store_new (
                 "Environment is not initialized. Did you call LSUP_init()?");
         return NULL;
     }
-    const LSUP_StoreInt *sif = select_interface (store_type);
+    const LSUP_StoreInt *sif = LSUP_store_int (store_type);
     if (UNLIKELY (!sif)) {
         log_error ("Not a valid store type: %d", store_type);
         return NULL;
@@ -56,6 +44,7 @@ void
 LSUP_store_free (LSUP_Store *store)
 {
     store->sif->free_fn (store->data);
+    if (store->id) free (store->id);
     free (store);
 }
 

+ 153 - 44
src/store_mdb.c

@@ -120,7 +120,7 @@ typedef struct mdbstore_iter_t {
  * Lookup DBs. These are indices and may be destroyed and rebuilt.
  */
 #define LOOKUP_TABLE \
-/*          #ID pfx #DB label  #Flags   */ \
+/*          #ID pfx  #DB label #Flags   */ \
     ENTRY(  S_PO,    "s:po",   DUPFIXED_MASK    )   /* 1-bound lookup */    \
     ENTRY(  P_SO,    "p:so",   DUPFIXED_MASK    )   /* 1-bound lookup */    \
     ENTRY(  O_SP,    "o:sp",   DUPFIXED_MASK    )   /* 1-bound lookup */    \
@@ -435,12 +435,7 @@ mdbstore_new (const char *id, size_t _unused)
         mdbstore_nsm_put (store, LSUP_default_nsm, txn);
 
         // Index default context.
-        // Create a dummy iterator just to use the current txn.
-        MDBIterator *it;
-        CALLOC_GUARD (it, NULL);
-        it->txn = txn;
-        mdbstore_add_term (store, LSUP_default_ctx_buf, it);
-        free (it);
+        mdbstore_add_term (store, LSUP_default_ctx_buf, txn);
     }
 
     store->flags |= LSSTORE_OPEN;
@@ -694,31 +689,6 @@ mdbstore_add_abort (void *h)
 }
 
 
-#if 0
-/* TODO deprecate. Use low-level instead and abstract at graph level. */
-static LSUP_rc
-mdbstore_add (
-        void *h, const LSUP_Buffer *sc,
-        const LSUP_BufferTriple strp[], const size_t ct, size_t *inserted)
-{
-    MDBStore *store = h;
-    MDBIterator *it = mdbstore_add_init (store, sc);
-    if (UNLIKELY (!it)) return LSUP_DB_ERR;
-
-    for (size_t i = 0; i < ct; i++) {
-        LSUP_rc rc = mdbstore_add_iter (it, strp + i);
-        if (UNLIKELY (rc < 0)) {
-            mdbstore_add_abort (it);
-            return rc;
-        }
-    }
-    *inserted = it->i;
-
-    return mdbstore_add_done (it);
-}
-#endif
-
-
 static LSUP_rc
 key_to_sterm (MDBIterator *it, const LSUP_Key key, LSUP_Buffer *sterm)
 {
@@ -990,6 +960,145 @@ mdbiter_free (void *h)
 }
 
 
+static LSUP_rc
+mdbstore_update_ctx (
+        void *h, const LSUP_Buffer *old_c, const LSUP_Buffer *new_c, void *th)
+{
+    LSUP_rc rc = LSUP_NOACTION;
+    MDBStore *store = h;
+    unsigned char *trp_data = NULL;
+
+    LSUP_Key
+        old_ck = LSUP_buffer_hash (old_c),
+        new_ck = LSUP_buffer_hash (new_c);
+    // lu_key, lu_data look up all triples with old context in c:spo, and
+    // replace old c with new c.
+    MDB_txn
+        *p_txn = th,
+        *txn;
+    CHECK (
+            rc = mdb_txn_begin (store->env, p_txn, 0, &txn),
+            finally);
+
+    MDB_cursor *i_cur, *d_cur;
+    CHECK (
+            rc = mdb_cursor_open (txn, store->dbi[IDX_C_SPO], &i_cur),
+            close_txn);
+    // TODO error handling.
+
+    MDB_val key, data;
+
+    // Return error if the graph URI already exists.
+    key.mv_data = &new_ck;
+    key.mv_size = KLEN;
+    rc = mdb_cursor_get (i_cur, &key, &data, MDB_FIRST_DUP);
+    if (rc == MDB_SUCCESS) {
+        log_error (
+                "Context key %lu already exists. Not replacing old graph.",
+                new_ck);
+        rc = LSUP_CONFLICT;
+        goto close_i;
+    }
+
+    // Add new context term.
+    CHECK (rc = mdbstore_add_term (store, new_c, txn), close_i);
+
+    key.mv_data = &old_ck;
+    // Count triples in cursor.
+    rc = mdb_cursor_get (i_cur, &key, &data, MDB_SET);
+    if (rc == MDB_NOTFOUND) {
+        log_info ("No triples found associated with old context.");
+        rc = LSUP_NOACTION;
+        goto close_i;
+    }
+    if (rc != MDB_SUCCESS) {
+        rc = LSUP_DB_ERR;
+        goto close_i;
+    }
+
+    // From here on, it can only be LSUP_OK or error.
+    rc = LSUP_OK;
+    size_t trp_ct;
+    CHECK (rc = mdb_cursor_count (i_cur, &trp_ct), close_i);
+    trp_data = malloc (trp_ct * TRP_KLEN);
+    if (UNLIKELY (!trp_data)) {
+        rc = LSUP_MEM_ERR;
+        goto close_i;
+    }
+
+    // Copy triple data as one block to temp buffer so that entries can be
+    // deleted while cursors are active.
+    rc = mdb_cursor_get (i_cur, &key, &data, MDB_GET_MULTIPLE);
+    if (rc != MDB_SUCCESS) {
+        rc = rc == MDB_NOTFOUND ? LSUP_NOACTION : LSUP_DB_ERR;
+        goto close_i;
+    }
+    size_t loc_cur = 0;
+    do {
+        memcpy (trp_data + loc_cur, data.mv_data, data.mv_size);
+        loc_cur += data.mv_size;
+    } while (mdb_cursor_get (
+                i_cur, &key, &data, MDB_NEXT_MULTIPLE) == MDB_SUCCESS);
+
+    // Zap c:spo entries in one go.
+    key.mv_data = &old_ck;
+    key.mv_size = KLEN;
+    data.mv_size = TRP_KLEN;
+    CHECK (rc = mdb_cursor_get (i_cur, &key, NULL, MDB_SET), close_i);
+    CHECK (rc = mdb_cursor_del (i_cur, MDB_NODUPDATA), close_i);
+
+    // Re-ad c:spo data individually.
+    key.mv_data = &new_ck;
+    for (size_t i = 0; i < trp_ct; i++) {
+        data.mv_data = trp_data + i * data.mv_size;
+        CHECK (
+                rc = mdb_cursor_put (i_cur, &key, &data, MDB_APPENDDUP),
+                close_i);
+    }
+    // Re-add c:spo data in bulk from buffer with new context.
+    // FIXME this is not working. Replaced by the for loop above.
+    /*
+    MDB_val data_block[] = {
+        { .mv_data = &new_ck, .mv_size = TRP_KLEN },
+        { .mv_data = NULL, .mv_size = trp_ct },
+    };
+    db_rc = mdb_cursor_put (i_cur, &key, data_block, MDB_MULTIPLE);
+    */
+
+    // Main table.
+    // Replace spo:c values one by one.
+    CHECK (rc = mdb_cursor_open (txn, store->dbi[IDX_SPO_C], &d_cur), close_i);
+    key.mv_size = TRP_KLEN;
+    data.mv_size = KLEN;
+    for (size_t i = 0; i < trp_ct; i++) {
+        key.mv_data = trp_data + i * key.mv_size;
+        data.mv_data = &old_ck;
+        CHECK (
+                rc = mdb_cursor_get (d_cur, &key, &data, MDB_GET_BOTH),
+                close_d);
+        CHECK (rc = mdb_cursor_del (d_cur, 0), close_d);
+        data.mv_data = &new_ck;
+        CHECK (
+                rc = mdb_cursor_put (d_cur, &key, &data, MDB_NOOVERWRITE),
+                close_d);
+    }
+
+close_d:
+    mdb_cursor_close (d_cur);
+close_i:
+    mdb_cursor_close (i_cur);
+close_txn:
+    if (rc == LSUP_OK) {
+        RCCK (mdb_txn_commit (txn));
+    } else mdb_txn_abort (txn);
+
+    if (trp_data) free (trp_data);
+finally:
+
+    return rc;
+}
+
+
 static LSUP_rc
 mdbstore_remove (
         void *h, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
@@ -1118,26 +1227,24 @@ mdbstore_tkey_exists (MDBStore *store, LSUP_Key tkey)
  *
  * @param[in] sterm Serialized term to store.
  *
- * @param[in] ith #MDBIterator handle. Only the transaction handle inside this
- * is used. It may be NULL, in which case a new transaction is opened and
- * closed for the operation.
+ * @param[in] th transaction handle. It may be NULL, in which case a new
+ * transaction is opened and closed for the operation.
  *
  * @return LSUP_OK on success; <0 on error.
  */
 static LSUP_rc
-mdbstore_add_term (void *h, const LSUP_Buffer *sterm, void *ith)
+mdbstore_add_term (void *h, const LSUP_Buffer *sterm, void *th)
 {
     //LOG_TRACE("Adding term to MDB store: %s", sterm->addr);
     MDBStore *store = h;
     int db_rc;
     MDB_val key, data;
 
-    MDBIterator *it = ith;
     MDB_txn *txn;
-    // If a transaction is active in the iterator, use it, otherwise open and
+    // If an active transaction was passed, use it, otherwise open and
     // close a new one.
-    bool borrowed_txn = (it && it->txn);
-    if (borrowed_txn) txn = it->txn;
+    bool borrowed_txn = (th != NULL);
+    if (borrowed_txn) txn = th;
     else RCCK (mdb_txn_begin (store->env, NULL, 0, &txn));
 
     MDB_cursor *cur;
@@ -1186,6 +1293,8 @@ const LSUP_StoreInt mdbstore_int = {
     .add_done_fn    = mdbstore_add_done,
     .add_term_fn    = mdbstore_add_term,
 
+    .update_ctx_fn  = mdbstore_update_ctx,
+
     .lookup_fn      = mdbstore_lookup,
     .lu_next_fn     = mdbiter_next,
     .lu_free_fn     = mdbiter_free,
@@ -1412,9 +1521,7 @@ it_next_2bound (MDBIterator *it)
  */
 inline static void
 it_next_3bound (MDBIterator *it)
-{
-    it->rc = MDB_NOTFOUND;
-}
+{ it->rc = MDB_NOTFOUND; }
 
 
 /* * * Term-specific lookups. * * */
@@ -1446,7 +1553,7 @@ lookup_0bound (MDBIterator *it, size_t *ct)
 
             *ct = stat.ms_entries;
         }
-        LOG_DEBUG("Found %lu keys.", *ct);
+        LOG_DEBUG("Found %lu triples.", *ct);
     }
 
     it->rc = mdb_cursor_open (it->txn, it->store->dbi[IDX_SPO_C], &it->cur);
@@ -1523,6 +1630,7 @@ lookup_1bound (uint8_t idx0, MDBIterator *it, size_t *ct)
             it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
             if (it->rc == MDB_SUCCESS) mdb_cursor_count (it->cur, ct);
         }
+        LOG_DEBUG("Found %lu triples.", *ct);
     }
 
     it->i = 0;
@@ -1622,6 +1730,7 @@ lookup_2bound(uint8_t idx0, uint8_t idx1, MDBIterator *it, size_t *ct)
             it->rc = mdb_cursor_get (it->cur, &it->key, &it->data, MDB_SET);
             if (it->rc == MDB_SUCCESS) mdb_cursor_count (it->cur, ct);
         }
+        LOG_DEBUG("Found %lu triples.", *ct);
     }
 
     it->i = 0;

+ 2 - 0
test.c

@@ -5,6 +5,7 @@
 #include "test_codec_nt.c"
 #include "test_codec_ttl.c"
 #include "test_store.c"
+#include "test_store_mdb.c"
 #include "test_graph.c"
 
 #define TEST_STORE_PATH TMPDIR "/lsup_test_mdb"
@@ -28,6 +29,7 @@ int main(int argc, char **argv) {
         term_tests() ||
         namespace_tests() ||
         store_tests() ||
+        store_mdb_tests() ||
         graph_tests() ||
         codec_nt_tests() ||
         codec_ttl_tests() ||

+ 74 - 37
test/test_graph.c

@@ -450,6 +450,66 @@ _graph_txn (LSUP_StoreType type)
 }
 
 
+static int
+_graph_relative (LSUP_StoreType type)
+{
+    const LSUP_StoreInt *sif = LSUP_store_int (type);
+
+    if (sif->setup_fn) sif->setup_fn (NULL, true);
+
+    LSUP_Term
+        *s = LSUP_iriref_new ("http://onto.knowledgetx.com/gr1/s1", NULL),
+        *s2 = LSUP_iriref_new ("http://onto.knowledgetx.com/gr2/s1", NULL),
+        *p = LSUP_iriref_new ("http://onto.knowledgetx.com/vocab/p1", NULL),
+        *o = LSUP_iriref_new ("http://onto.knowledgetx.com/gr1/o1", NULL),
+        *o2 = LSUP_iriref_new ("http://onto.knowledgetx.com/gr2/o1", NULL),
+        *c = LSUP_iriref_new ("http://onto.knowledgetx.com/gr1/", NULL),
+        *rel_s = LSUP_iriref_relative (c, s),
+        *rel_o = LSUP_iriref_relative (c, o);
+    LSUP_Triple *spo[2] = {
+        LSUP_triple_new (s, p, o),
+        NULL
+    };
+    LSUP_Triple *rel_spo = LSUP_triple_new (rel_s, p, rel_o);
+
+    LSUP_Store *store =
+        type == LSUP_STORE_HTABLE ? NULL
+        : LSUP_store_new (type, NULL, 0);
+
+    LSUP_Graph *gr = LSUP_graph_new (store, c, NULL);
+
+    size_t ct;
+    LSUP_graph_add (gr, spo, &ct);
+
+    // Both absolute and relative IRIs should be found.
+    ASSERT (LSUP_graph_contains (gr, rel_spo), "Relative triple not found!");
+    ASSERT (LSUP_graph_contains (gr, spo[0]), "Absolute triple not found!");
+
+    // Change graph URI and verify that relative URIs are still found, and
+    // that absolute URIs change with it.
+    spo[0]->s = s2;
+    LSUP_term_free (s);
+    spo[0]->o = o2;
+    LSUP_term_free (o);
+
+    LSUP_graph_set_uri (gr, LSUP_iriref_new (
+                "http://onto.knowledgetx.com/gr2/", NULL));
+    LSUP_term_free (c);
+
+    ASSERT (LSUP_graph_contains (gr, rel_spo), "Relative triple not found!");
+    ASSERT (LSUP_graph_contains (gr, spo[0]), "Absolute triple not found!");
+
+    LSUP_triple_free (spo[0]);
+    LSUP_term_free (rel_s);
+    LSUP_term_free (rel_o);
+    free (rel_spo);
+    LSUP_graph_free (gr);
+    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+
+    return 0;
+}
+
+
 static int
 test_environment()
 {
@@ -547,6 +607,20 @@ BACKEND_TBL
 }
 
 
+static int test_graph_relative()
+{
+    /*
+     * Test relative URIs in graphs.
+     */
+#define ENTRY(a, b) \
+    if (_graph_relative (LSUP_STORE_##a) != 0) return -1;
+BACKEND_TBL
+#undef ENTRY
+
+    return 0;
+}
+
+
 static int test_graph_copy()
 {
     LSUP_Triple **trp = create_triples();
@@ -589,43 +663,6 @@ static int test_graph_copy()
 }
 
 
-static int test_graph_relative()
-{
-    LSUP_Term
-        *s = LSUP_iriref_new ("http://onto.knowledgetx.net/gr1/s1", NULL),
-        *p = LSUP_iriref_new ("http://onto.knowledgetx.net/vocab/p1", NULL),
-        *o = LSUP_iriref_new ("http://onto.knowledgetx.net/gr1/o1", NULL),
-        *c = LSUP_iriref_new ("http://onto.knowledgetx.net/gr1/", NULL),
-        *rel_s = LSUP_iriref_relative (c, s),
-        *rel_o = LSUP_iriref_relative (c, o);
-    LSUP_Triple *spo[2] = {
-        LSUP_triple_new (s, p, o),
-        NULL
-    };
-    LSUP_Triple *rel_spo = LSUP_triple_new (rel_s, p, rel_o);
-
-    LSUP_Store *store = LSUP_store_new (LSUP_STORE_MDB, NULL, 0);
-    LSUP_Graph *gr = LSUP_graph_new (store, c, NULL);
-
-    size_t ct;
-    LSUP_graph_add (gr, spo, &ct);
-
-    // Both absolute and relative IRIs should be found.
-    ASSERT (LSUP_graph_contains (gr, rel_spo), "Relative triple not found!");
-    ASSERT (LSUP_graph_contains (gr, spo[0]), "Absolute triple not found!");
-
-    LSUP_triple_free (spo[0]);
-    LSUP_term_free (rel_s);
-    LSUP_term_free (rel_o);
-    LSUP_term_free (c);
-    free (rel_spo);
-    LSUP_graph_free (gr);
-    LSUP_store_free (store);
-
-    return 0;
-}
-
-
 int graph_tests()
 {
     RUN (test_environment);

+ 65 - 313
test/test_store_mdb.c

@@ -1,323 +1,81 @@
-#include "test.h"
-#include "store.h"
-#include "assets/triples.h"
-
-
-#define STORE_ID_MDB "file:///tmp/testdb";
-#define STORE_ID_HTABLE LSUP_NS "dummy";
-
-static LSUP_Store store_s;
-static LSUP_Store *store = &store_s;
-
-
-/** @brief Test triple store.
- */
-static int test_triple_store()
-{
-    EXPECT_PASS (LSUP_mdbstore_setup (path, true));
-
-    LSUP_MDBStore *store = LSUP_mdbstore_new (path, NULL); // triple store.
-    ASSERT (store != NULL, "Error initializing store!");
-
-    LSUP_Triple *trp = create_triples();
-    LSUP_BufferTriple ser_trp[NUM_TRP];
-
-    for (int i = 0; i < NUM_TRP; i++) {
-        LSUP_BufferTriple *tmp = LSUP_triple_serialize (trp + i);
-        ser_trp[i] = *tmp;
-        free (tmp);
-    }
-
-    // Test adding.
-    size_t ins;
-    EXPECT_PASS (LSUP_mdbstore_add (store, NULL, ser_trp, NUM_TRP, &ins));
-    EXPECT_INT_EQ (ins, 8);
-    EXPECT_INT_EQ (LSUP_mdbstore_size (store), 8);
-
-    // Test lookups.
-    LSUP_Buffer *lut[12][3] = {
-        {NULL, NULL, NULL},
-
-        {ser_trp[0].s, NULL, NULL},
-        {ser_trp[2].s, NULL, NULL},
-        {NULL, ser_trp[0].p, NULL},
-        {NULL, ser_trp[0].s, NULL},
-        {NULL, NULL, ser_trp[6].o},
-
-        {ser_trp[4].s, ser_trp[4].p, NULL},
-        {NULL, ser_trp[7].p, ser_trp[7].o},
-        {ser_trp[5].s, NULL, ser_trp[5].o},
-        {ser_trp[5].s, NULL, ser_trp[5].o},
-
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[4].o},
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[6].o},
-    };
-
-    LSUP_Buffer *luc[12] = {
-        NULL,
-
-        NULL,
-        ser_trp[2].s,
-        NULL,
-        NULL,
-        NULL,
+#include <unistd.h>
 
-        NULL,
-        NULL,
-        NULL,
-        ser_trp[2].s,
-
-        NULL,
-        NULL,
-    };
-
-    size_t results[12] = {
-        8,
-        5, 1, 1, 0, 1,
-        2, 1, 2, 2,
-        1, 0,
-    };
-
-    for (int i = 0; i < 12; i++) {
-        size_t ct;
-        log_info ("Testing triple lookup #%d.", i);
-
-        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);
-    }
+#include "test.h"
+#include "store_mdb.h"
 
-    for (int i = 0; i < NUM_TRP; i++) {
-        LSUP_buffer_free (ser_trp[i].s);
-        LSUP_buffer_free (ser_trp[i].p);
-        LSUP_buffer_free (ser_trp[i].o);
-    }
 
-    LSUP_mdbstore_free (store);
-    free_triples (trp);
+#define MDBSTORE_ID "file:///tmp/testdb"
 
-    return 0;
-}
 
-
-/** @brief Test quad store.
- *
- * Insert the same triple set twice with different contexts.
+/** @brief Test context switching.
  */
-static int test_quad_store()
+static int test_ctx_switch()
 {
-    EXPECT_PASS (LSUP_mdbstore_setup (path, true));
-
-    LSUP_Term *ctx1 = LSUP_iriref_new("urn:c:1", NULL);
-    LSUP_Buffer *sc1 = LSUP_term_serialize (ctx1);
+    const LSUP_StoreInt *sif = LSUP_store_int (LSUP_STORE_MDB);
+    sif->setup_fn (MDBSTORE_ID, true);
 
-    LSUP_MDBStore *store = LSUP_mdbstore_new (path, sc1); // quad store.
+    LSUP_Store *store = LSUP_store_new (LSUP_STORE_MDB, MDBSTORE_ID, 0);
     ASSERT (store != NULL, "Error initializing store!");
 
-    LSUP_Triple *trp = create_triples();
-    LSUP_BufferTriple ser_trp[NUM_TRP];
-
-    for (int i = 0; i < NUM_TRP; i++) {
-        LSUP_BufferTriple *tmp = LSUP_triple_serialize (trp + i);
-        ser_trp[i] = *tmp;
-        free (tmp);
+    // Create enough triples to test a multi-page copy of triple data.
+    // Add small buffer (4) to create a 3rd page.
+    size_t num_trp = (getpagesize() * 2 / TRP_KLEN) + 4;
+    LSUP_BufferTriple **tdata = malloc (num_trp * sizeof (*tdata));
+    LSUP_Triple *trp = LSUP_triple_new (
+        LSUP_iriref_new ("urn:s:1", NULL),
+        LSUP_iriref_new ("urn:p:1", NULL),
+        NULL);
+    char *o_str = malloc (8 * sizeof (*o_str));
+    if (UNLIKELY (!o_str)) return LSUP_MEM_ERR;
+
+    for (unsigned int i = 0; i < num_trp; i++) {
+        sprintf (o_str, "%.7d", i);
+        if (trp->o) LSUP_term_free (trp->o);
+        trp->o = LSUP_literal_new (o_str, NULL);
+        tdata[i] = LSUP_triple_serialize (trp);
     }
+    LSUP_triple_free (trp);
+    free (o_str);
+    LOG_DEBUG ("Created %lu triples.", num_trp);
+
+    LSUP_Term
+        *c1 = LSUP_iriref_new ("urn:c:1", NULL),
+        *c2 = LSUP_iriref_new ("urn:c:2", NULL);
+    LSUP_Buffer
+        *sc1 = LSUP_term_serialize (c1),
+        *sc2 = LSUP_term_serialize (c2);
+    LSUP_term_free (c1);
+    LSUP_term_free (c2);
+
+    void *it = sif->add_init_fn (store->data, sc1, NULL);
+    for (size_t i = 0; i < num_trp; i++)
+        sif->add_iter_fn (it, tdata[i]);
+    sif->add_done_fn (it);
+
+    for (size_t i = 0; i < num_trp; i++)
+        LSUP_btriple_free (tdata[i]);
+    free (tdata);
+
+    size_t check_ct;
+    ASSERT (
+            it = sif->lookup_fn (
+                store->data, NULL, NULL, NULL, sc1, NULL, &check_ct
+            ), "Error looking up triples!");
+    EXPECT_INT_EQ (check_ct, num_trp);
+    sif->lu_free_fn (it);
+
+    RCCK (sif->update_ctx_fn (store->data, sc1, sc2, NULL));
+    ASSERT (
+            it = sif->lookup_fn (
+                store->data, NULL, NULL, NULL, sc2, NULL, &check_ct
+            ), "Error looking up triples!");
+    EXPECT_INT_EQ (check_ct, num_trp);
+    sif->lu_free_fn (it);
 
-    // Only triples 0÷5 in default context.
-    size_t ct;
-    EXPECT_PASS (LSUP_mdbstore_add (store, NULL, ser_trp, 6, &ct));
-    EXPECT_INT_EQ (ct, 6);
-
-    LSUP_Term *ctx2 = LSUP_iriref_new("urn:c:2", NULL);
-    LSUP_Buffer *sc2 = LSUP_term_serialize (ctx2);
-
-    // Only triples 4÷9 in context 2 (effectively 4 non-duplicates).
-    EXPECT_PASS (LSUP_mdbstore_add (store, sc2, ser_trp + 4, 6, &ct));
-    EXPECT_INT_EQ (ct, 4);
-
-    // 6 triples in ctx1 + 6 in ctx2 - 2 duplicates
-    EXPECT_INT_EQ (LSUP_mdbstore_size (store), 10);
-
-    // This context has no triples.
-    LSUP_Term *ctx3 = LSUP_iriref_new("urn:c:3", NULL);
-    LSUP_Buffer *sc3 = LSUP_term_serialize (ctx3);
-
-    // Test lookups.
-    LSUP_Buffer *lut[41][3] = {
-        // Any context
-        {NULL, NULL, NULL},                                 // #0
-
-        {ser_trp[0].s, NULL, NULL},                         // #1
-        {NULL, ser_trp[0].p, NULL},                         // #2
-        {NULL, ser_trp[0].s, NULL},                         // #3
-        {NULL, NULL, ser_trp[6].o},                         // #4
-
-        {ser_trp[4].s, ser_trp[4].p, NULL},                 // #5
-        {NULL, ser_trp[7].p, ser_trp[7].o},                 // #6
-        {ser_trp[5].s, NULL, ser_trp[5].o},                 // #7
-
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[4].o},         // #8
-        {ser_trp[2].s, ser_trp[4].p, ser_trp[5].o},         // #9
-
-        // Context 1 (trp[0÷5])
-        {NULL, NULL, NULL},                                 // #10
-
-        {ser_trp[0].s, NULL, NULL},                         // #11
-        {ser_trp[2].s, NULL, NULL},                         // #12
-        {NULL, ser_trp[0].p, NULL},                         // #13
-        {NULL, ser_trp[6].p, NULL},                         // #14
-        {NULL, NULL, ser_trp[6].o},                         // #15
-
-        {ser_trp[4].s, ser_trp[4].p, NULL},                 // #16
-        {NULL, ser_trp[7].p, ser_trp[7].o},                 // #17
-        {ser_trp[5].s, NULL, ser_trp[5].o},                 // #18
-
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[4].o},         // #19
-        {ser_trp[6].s, ser_trp[6].p, ser_trp[6].o},         // #20
-
-
-        // Context 2 (trp[4÷9])
-        {NULL, NULL, NULL},                                 // #21
-
-        {ser_trp[0].s, NULL, NULL},                         // #22
-        {NULL, ser_trp[0].p, NULL},                         // #23
-        {NULL, ser_trp[0].s, NULL},                         // #24
-        {NULL, NULL, ser_trp[6].o},                         // #25
-
-        {ser_trp[4].s, ser_trp[4].p, NULL},                 // #26
-        {NULL, ser_trp[7].p, ser_trp[7].o},                 // #27
-        {ser_trp[5].s, NULL, ser_trp[5].o},                 // #28
-
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[4].o},         // #29
-        {ser_trp[6].s, ser_trp[6].p, ser_trp[6].o},         // #30
-
-
-        // Non-existing context
-        {NULL, NULL, NULL},                                 // #31
-
-        {ser_trp[0].s, NULL, NULL},                         // #32
-        {NULL, ser_trp[0].p, NULL},                         // #33
-        {NULL, ser_trp[0].s, NULL},                         // #34
-        {NULL, NULL, ser_trp[6].o},                         // #35
-
-        {ser_trp[4].s, ser_trp[4].p, NULL},                 // #36
-        {NULL, ser_trp[7].p, ser_trp[7].o},                 // #37
-        {ser_trp[5].s, NULL, ser_trp[5].o},                 // #38
-
-        {ser_trp[4].s, ser_trp[4].p, ser_trp[4].o},         // #39
-        {ser_trp[6].s, ser_trp[6].p, ser_trp[6].o},         // #40
-    };
-
-    LSUP_Buffer *luc[41] = {
-        // Any context
-        NULL,                                               // #0
-        NULL, NULL, NULL, NULL,                             // #1-#4
-        NULL, NULL, NULL,                                   // #5-#7
-        NULL, NULL,                                         // #8-#9
-
-        // Context 1 (trp[0÷5])
-        sc1,                                                // #10
-        sc1, sc1, sc1, sc1, sc1,                            // #11-#15
-        sc1, sc1, sc1,                                      // #16-#18
-        sc1, sc1,                                           // #19-#20
-
-        // Context 2 (trp[4÷9])
-        sc2,                                                // #21
-        sc2, sc2, sc2, sc2,                                 // #22-#25
-        sc2, sc2, sc2,                                      // #26-#28
-        sc2, sc2,                                           // #29-#30
-
-        // Non-existing context
-        sc3,                                                // #31
-        sc3, sc3, sc3, sc3,                                 // #32-#35
-        sc3, sc3, sc3,                                      // #36-#38
-        sc3, sc3,                                           // #39-#40
-    };
-
-    size_t results[41] = {
-        // NULL ctx
-        8,                      // #0
-        5, 1, 0, 1,             // #1-#4
-        2, 1, 2,                // #5-#7
-        1, 0,                   // #8-#9
-
-        // ctx1
-        6,                      // #10
-        4, 1, 1, 0, 0,          // #11-#15
-        1, 0, 1,                // #16-#18
-        1, 0,                   // #19-#20
-
-        // ctx2
-        4,                      // #21
-        3, 0, 0, 1,             // #22-#25
-        2, 1, 2,                // #26-#28
-        1, 1,                   // #29-#30
-
-        // ctx3
-        0,                      // #31-#32
-        0, 0, 0, 0,             // #33-#36
-        0, 0, 0,                // #37-#39
-        0, 0,                   // #40
-    };
-
-    int ctx_ct[10] = {
-        // BEGIN ctx1 (triples 0÷5)
-        1, 1, 1, 1,
-        // BEGIN ctx2 (triples 4÷9)
-        2, 2,
-        // END ctx 1
-        1, 1, 1, 1,
-        // END ctx 2
-    };
-
-    for (int i = 0; i < 41; i++) {
-        size_t ct;
-
-        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]);
-
-        LSUP_mdbiter_free (it);
-    }
-
-    // Check triple contexts.
-    for (int i = 0; i < 10; i++) {
-        LSUP_MDBIterator *it = LSUP_mdbstore_lookup (
-                store, ser_trp[i].s, ser_trp[i].p, ser_trp[i].o, NULL, NULL);
-        log_info ("Checking contexts for triple %d.", i);
-        LSUP_Buffer *ctx_a;
-        EXPECT_PASS (LSUP_mdbiter_next (it, NULL, &ctx_a));
-        LSUP_mdbiter_free (it);
-
-        ASSERT (ctx_a != NULL, "No contexts found!");
-
-        size_t j = 0;
-        while (ctx_a[j].addr) j++;
-        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);
-        LSUP_buffer_free (ser_trp[i].o);
-    }
-
-    LSUP_term_free (ctx1);
-    LSUP_term_free (ctx2);
-    LSUP_term_free (ctx3);
     LSUP_buffer_free (sc1);
     LSUP_buffer_free (sc2);
-    LSUP_buffer_free (sc3);
-    free_triples (trp);
 
-    LSUP_mdbstore_free (store);
+    LSUP_store_free (store);
 
     return 0;
 }
@@ -325,13 +83,7 @@ static int test_quad_store()
 
 int store_mdb_tests()
 {
-#define ENTRY(a, b) \
-    store->type = LSUP_STORE_##a; \
-    store->id = STORE_ID_##a; \
-    store->sif = &b; \
-    RUN (test_triple_store); \
-    RUN (test_quad_store);
-BACKEND_TBL
-#undef ENTRY
+    RUN (test_ctx_switch);
+
     return 0;
 }