Browse Source

Change underlying context when changing graph URI.

Fix most documentation; update Doxyfile.
scossu 2 weeks ago
parent
commit
8d1515c2d8
16 changed files with 888 additions and 707 deletions
  1. 322 166
      Doxyfile
  2. 6 2
      include/buffer.h
  3. 7 4
      include/codec.h
  4. 1 1
      include/codec/parser_ttl.h
  5. 58 33
      include/core.h
  6. 38 22
      include/graph.h
  7. 10 3
      include/namespace.h
  8. 11 2
      include/store.h
  9. 64 26
      include/store_interface.h
  10. 38 32
      include/term.h
  11. 35 7
      src/graph.c
  12. 4 15
      src/store.c
  13. 153 44
      src/store_mdb.c
  14. 2 0
      test.c
  15. 74 37
      test/test_graph.c
  16. 65 313
      test/test_store_mdb.c

File diff suppressed because it is too large
+ 322 - 166
Doxyfile


+ 6 - 2
include/buffer.h

@@ -3,6 +3,11 @@
 
 #include "core.h"
 
+/** @defgroup buffer Buffer module
+ * @ingroup public
+ * @{
+ */
+
 /** @brief "NULL" key, a value that is never user-provided.
  *
  * Used to mark special values (e.g. deleted records).
@@ -218,8 +223,6 @@ inline bool LSUP_buffer_eq (const LSUP_Buffer *buf1, const LSUP_Buffer *buf2)
  *
  * @important The triple must be freed with #LSUP_btriple_free().
  *
- * @param[in] sspo Serialized triple pointer to initialize.
- *
  * @param[in] s Subject as a serialized buffer.
  *
  * @param[in] p Predicate as a serialized buffer.
@@ -323,4 +326,5 @@ LSUP_btriple_hash (const LSUP_BufferTriple *strp)
  */
 #define BTRP_DUMMY LSUP_btriple_new (BUF_DUMMY, BUF_DUMMY, BUF_DUMMY)
 
+/// @} END defgroup buffer
 #endif

+ 7 - 4
include/codec.h

@@ -3,7 +3,10 @@
 
 #include "graph.h"
 
-
+/** @defgroup codec RDF codec module
+ * @ingroup private
+ * @{
+ */
 /**
  * Max data size passed to the scanner and parser at each iteration.
  */
@@ -118,8 +121,8 @@ typedef void (*gr_encode_done_fn_t)(void *it);
  *
  * @param[in] nsm Namespace map handle.
  *
- * @param[out] Pointer to the term handle to be created. Implementaions SHOULD
- *  return NULL on a parse error.
+ * @param[out] term Pointer to the term handle to be created. Implementaions
+ *  SHOULD return NULL on a parse error.
  *
  * @return Implementations MUST return LSUP_OK on success and a negative value
  *  on parsing error.
@@ -295,5 +298,5 @@ uint8_t *unescape_unicode (const uint8_t *esc_str, size_t size);
  */
 char *fmt_header (char *pfx);
 
-
+/// @}  END defgroup codec
 #endif

+ 1 - 1
include/codec/parser_ttl.h

@@ -6,7 +6,7 @@
 
 /** @brief Parse a N-Triples document from a file handle.
  *
- * @param[in] doc N-Triples document.
+ * @param[in] stream N-Triples document.
  *
  * @param[out] gr Pointer to a graph handle to be created. The new graph will
  * have a random UUID URN.

+ 58 - 33
include/core.h

@@ -11,6 +11,7 @@
 #include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -40,15 +41,18 @@
 #define LIKELY(x)       __builtin_expect(!!(x), true)
 #define UNLIKELY(x)     __builtin_expect(!!(x), false)
 
-#define LSUP_NS "urn:lsup:" /// Default LS namespace.
-
 // TODO Cross-platform ramdisk path.
 #define TMPDIR "/tmp"
 
-#define KLEN sizeof(LSUP_Key)
-#define DBL_KLEN sizeof(LSUP_DoubleKey)
-#define TRP_KLEN sizeof(LSUP_TripleKey)
-#define QUAD_KLEN sizeof(LSUP_QuadKey)
+/** @defgroup public LSUP Public API
+ * @{
+ */
+#define LSUP_NS "urn:lsup:"             /// Default LS namespace.
+
+#define KLEN sizeof(LSUP_Key)           /// Key length.
+#define DBL_KLEN sizeof(LSUP_DoubleKey) /// Double key length.
+#define TRP_KLEN sizeof(LSUP_TripleKey) /// Triple key length.
+#define QUAD_KLEN sizeof(LSUP_QuadKey)  /// Quad key length.
 
 # define UUIDSTR_SIZE 37
 
@@ -59,9 +63,12 @@
 
 /* * * RETURN CODES * * */
 
-/**
+/**@defgroup rc Return codes
  * 0 is success, positive integers (>88800) are warnings, and negative integers
  * (>-88900) are errors.
+ *
+ * @ingroup public
+ * @{
  */
 typedef int LSUP_rc;
 
@@ -103,12 +110,12 @@ typedef int LSUP_rc;
  */
 #define LSUP_CONFLICT       88804
 
-/*
- * NOTE When adding new warning codes, use a value larger than the last one
+#define LSUP_MIN_WARNING    LSUP_NOACTION
+/**brief Last warning value. Used internally.
+ *
+ * @note When adding new warning codes, use a value larger than the last one
  * in the list. Also change LSUP_MAX_WARNING.
  */
-
-#define LSUP_MIN_WARNING    LSUP_NOACTION
 #define LSUP_MAX_WARNING    LSUP_CONFLICT
 
 /// Generic error return code.
@@ -132,7 +139,7 @@ typedef int LSUP_rc;
 /// I/O error.
 #define LSUP_IO_ERR         -88893
 
-/// Memory error.
+/// Memory allocation error.
 #define LSUP_MEM_ERR        -88892
 
 /** @brief Conflict error.
@@ -146,28 +153,30 @@ typedef int LSUP_rc;
 
 /// Error while handling environment setup.
 #define LSUP_ENV_ERR        -88890
+///@}  END defgroup return
 
-/*
- * NOTE When adding new error codes, use a value larger than the last one
- * in the list. Also change LSUP_MAX_ERROR.
+/** @defgroup hashing Hashing stuff
+ * @ingroup public
+ * @{
  */
 
-#define LSUP_MIN_ERROR      LSUP_ERROR
-#define LSUP_MAX_ERROR      LSUP_ENV_ERR
-
-// Hashing stuff.
-
 #ifndef LSUP_HASH_SEED
-/** @brief Seed used for all hashing. Compile-time configurable.
- */
+/// Seed used for all hashing. Compile-time configurable.
 #define LSUP_HASH_SEED 0
 #endif
 
+/// Default 32-bit hashing function.
 #define LSUP_HASH32(buf, size, seed) XXH32 (buf, size, seed)
+/// Default 64-bit hashing function.
 #define LSUP_HASH64(buf, size, seed) XXH3_64bits_withSeed (buf, size, seed)
+/// Default 128-bit hashing function.
 #define LSUP_HASH128(buf, size, seed) XXH3_128bits_withSeed (buf, size, seed)
-// TODO Add 32-bit switch.
-#define LSUP_HASH(buf, size, seed) LSUP_HASH64 (buf, size, seed)
+/// Default hashing function. Depends on architecture.
+#if INTPTR_MAX == INT64_MAX
+#define LSUP_HASH(...) LSUP_HASH64 (__VA_ARGS__)
+#else
+#define LSUP_HASH(...) LSUP_HASH32 (__VA_ARGS__)
+#endif
 
 
 extern char *warning_msg[], *error_msg[];
@@ -184,10 +193,13 @@ typedef XXH128_hash_t LSUP_Hash128;
 /** @brief Default hash data type.
  *
  * This is 64 bit long for 64-bit systems.
- *
- * @todo Add 32-bit switch.
  */
+#if INTPTR_MAX == INT64_MAX
 typedef LSUP_Hash64 LSUP_Hash;
+#else
+typedef LSUP_Hash32 LSUP_Hash;
+#endif
+///@}  END defgroup hashing
 
 /// Boolean operations that can be performed on a graph.
 typedef enum {
@@ -222,7 +234,7 @@ mkdir_p (const char *path, mode_t mode);
 
 /** @brief Remove a directory recursively, as in Unix "rm -r".
  *
- * @param path[in] Path of directory to remove.
+ * @param[in] path Path of directory to remove.
  */
 LSUP_rc
 rm_r (const char *path);
@@ -232,8 +244,20 @@ rm_r (const char *path);
  */
 const char *
 LSUP_strerror (LSUP_rc rc);
+/// @} END defgroup public
 
 
+/** @defgroup private Private LSUP API
+ * @{
+ */
+/// minimum error value.
+#define LSUP_MIN_ERROR      LSUP_ERROR
+/**brief minimum error value.
+ * @note When adding new error codes, use a value larger than the last one
+ * in the list. Also change LSUP_MAX_ERROR.
+ */
+#define LSUP_MAX_ERROR      LSUP_ENV_ERR
+
 /** @brief Encode a code point using UTF-8.
  *
  * https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
@@ -314,42 +338,42 @@ inline int utf8_encode (const uint32_t utf, unsigned char *out)
     else if ((rc) > 0) LOG_DEBUG(LSUP_strerror (rc));               \
 } while (0);
 
-/// Error handling via goto.
+/// Jump to `marker` if `exp` does not return `LSUP_OK`.
 #define CHECK(exp, marker) do {                                     \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
     if (UNLIKELY (_rc != LSUP_OK)) goto marker;                     \
 } while (0);
 
-/// Jump if rc is negative (skip warnings).
+/// Jump to `marker` if `exp` returns a negative value (skip warnings).
 #define PCHECK(exp, marker) do {                                    \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
     if (UNLIKELY (_rc < LSUP_OK)) goto marker;                      \
 } while (0);
 
-/// Return rc if it is of LSUP_rc type and nonzero.
+/// Return `exp` return value if it is of `LSUP_rc` type and nonzero.
 #define RCCK(exp) do {                                              \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
     if (UNLIKELY (_rc != LSUP_OK)) return _rc;                       \
 } while (0);
 
-/// Return rc if it is of LSUP_rc type and negative (=error)
+/// Return `exp` return value if it is of `LSUP_rc` type and negative (=error)
 #define PRCCK(exp) do {                                              \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
     if (UNLIKELY (_rc < LSUP_OK)) return _rc;                       \
 } while (0);
 
-/// Return NULL if RC is nonzero.
+/// Return `NULL` if `exp` returns a nonzero value.
 #define RCNL(exp) do {                                              \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
     if (UNLIKELY (_rc != LSUP_OK)) return NULL;                      \
 } while (0);
 
-/// Return NULL if RC is negative (=error)
+/// Return NULL if `exp` returns a negative value (=error)
 #define PRCNL(exp) do {                                              \
     LSUP_rc _rc = (exp);                                            \
     LOG_RC(_rc);                                                    \
@@ -374,5 +398,6 @@ inline int utf8_encode (const uint32_t utf, unsigned char *out)
 #define MALLOC_GUARD_NL(var) MALLOC_GUARD((var), NULL)              \
 #define CALLOC_GUARD_NL(var) CALLOC_GUARD((var), NULL)              \
 */
+/// @} END defgroup private
 
 #endif  /* _LSUP_CORE_H */

+ 38 - 22
include/graph.h

@@ -6,6 +6,11 @@
 #include "term.h"
 
 
+/** @defgroup graph RDF graph module
+ * @ingroup public
+ * @{
+ */
+
 /** @brief Graph object.
  */
 typedef struct graph_t LSUP_Graph;
@@ -46,6 +51,9 @@ LSUP_graph_new (LSUP_Store *store, const LSUP_Term *uri, LSUP_NSMap *nsm);
  * found in the store with the given context URI. The new graph URI is the
  * same as the given context.
  *
+ * @param[in] txn Transaction handle. If not `NULL`, new child transaction will
+ *  be opened under the one provided.
+ *
  * @param[in] store Back end store handle. The store must exist.
  *
  * @param[in] uri URI of the graph to retrieve.
@@ -75,9 +83,9 @@ LSUP_graph_get_txn (
  * @param[in] txn Transaction handle. It may be NULL, or an open transaction
  *  handle, in which case the copy is done within the specified transaction.
  *
- * @param src[in] Source graph.
+ * @param[in] src Source graph.
  *
- * @param dest[in] Destination graph.
+ * @param[in] dest Destination graph.
  *
  * @param[in] s, p, o Terms to look up for filtering. Any and all terms can be
  * NULL, which indicate unbound terms.
@@ -101,9 +109,9 @@ LSUP_graph_copy_contents_txn (
  * This is a shortcut for #LSUP_graph_copy_contents_txn(). If you need to
  * specify a transaction handle and/or a filter for the copy, use that.
  *
- * @param src[in] Source graph.
+ * @param[in] src Source graph.
  *
- * @param dest[in] Destination graph.
+ * @param[in] dest Destination graph.
  *
  * @return LSUP_OK on success; LSUP_NOACTION if no triples were copied; <0
  *  if an error occurred.
@@ -121,13 +129,13 @@ LSUP_graph_copy_contents_txn (
  * @param[in] txn R/W transaction handle for the destination store.  It may be
  * NULL, or an open transaction within which the operation is performed.
  *
- * @param op[in] Operation to perform. One of #LSUP_bool_op.
+ * @param[in] op Operation to perform. One of #LSUP_bool_op.
  *
- * @param gr1[in] First operand.
+ * @param[in] gr1 First operand.
  *
- * @param gr2[in] Second operand.
+ * @param[in] gr2 Second operand.
  *
- * @param res[out] Result graph. The handle should be initialized via
+ * @param[out] res Result graph. The handle should be initialized via
  *  #LSUP_graph_new() or equivalent. Any preexisting contents are not removed.
  *  If an unrecoverable error occurs, this graph is freed, and any preexisting
  *  triples are lost. Therefore, reusing a result graph handle should only be
@@ -183,14 +191,15 @@ 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.
+ * @param[in] gr Graph handle.
  *
- * @param uri[in] IRI handle. The graph takes ownership of the handle.
+ * @param[in] uri IRI handle. The graph takes ownership of the handle.
  *
  * @return LSUP_OK on success; <0 on error.
  */
@@ -250,7 +259,7 @@ LSUP_GraphIterator *
 LSUP_graph_add_init_txn (void *txn, LSUP_Graph *gr);
 
 
-/// Non-transactional version of #LSUP_graph_init_txn().
+/// Non-transactional version of #LSUP_graph_add_init_txn().
 #define LSUP_graph_add_init(...) LSUP_graph_add_init_txn (NULL, __VA_ARGS__)
 
 
@@ -300,7 +309,7 @@ LSUP_graph_add_txn (
  *
  * @param[in] txn Transaction handle. It may be NULL.
  *
- * @param[in] gr[in] Graph to delete triples from.
+ * @param[in] gr Graph to delete triples from.
  *
  * @param[in] s, p, o Matching pattern. Any and all of s, p, o can be NULL.
  *
@@ -323,14 +332,20 @@ LSUP_graph_remove_txn (
  *
  * @param[in] gr Graph to look up.
  *
- * @param[in] s, p, o Terms to look for. Any and all terms can be NULL, which
- *  indicate unbound terms.
+ * @param[in] s Subject to look for. It can be NULL, which indicates an
+ * unbound term.
+ *
+ * @param[in] p Predicate to look for. It can be NULL, which indicates an
+ * unbound term.
+ *
+ * @param[in] o Object to look for. It can be NULL, which indicates an
+ * unbound term.
  *
  * @param[out] ct If not NULL, this handle is populated with the number of
  *  entries found.
  *
  * @return Pointer to a #LSUP_GraphIterator to be generated. It must be
- *  freed with #LSUP_graph_iter_free after use.
+ *  freed with #LSUP_graph_iter_free() after use.
  */
 LSUP_GraphIterator *
 LSUP_graph_lookup_txn (void *txn, const LSUP_Graph *gr, const LSUP_Term *s,
@@ -343,9 +358,9 @@ LSUP_graph_lookup_txn (void *txn, const LSUP_Graph *gr, const LSUP_Term *s,
 
 /** @brief Advance a cursor obtained by a lookup and return a matching triple.
  *
- * @param it[in] Iterator handle obtained through #LSUP_graph_lookup_txn.
+ * @param[in] it Iterator handle obtained through #LSUP_graph_lookup_txn.
  *
- * @param spo[out] Triple handle pointer to be populated with the next result.
+ * @param[out] spo Triple handle pointer to be populated with the next result.
  * If not NULL, it will allocate a new triple and new terms, and should be
  * freed with LSUP_triple_free().
  *
@@ -388,7 +403,7 @@ LSUP_graph_iter_free (LSUP_GraphIterator *it);
  * @param[in] type Type of connections to look up.
  *
  * @return Link map for the requested term. It should be freed with
- *  #LSUP_conn_list_free().
+ *  #LSUP_link_map_free().
  */
 LSUP_LinkMap *
 LSUP_graph_connections (
@@ -458,4 +473,5 @@ LSUP_graph_add_link_map (
 LSUP_Term *
 LSUP_bnode_add_collection (LSUP_GraphIterator *it, LSUP_TermSet *ts);
 
+/// @}  END defgroup graph
 #endif

+ 10 - 3
include/namespace.h

@@ -5,6 +5,12 @@
 
 #include "core.h"
 
+
+/** @defgroup namespace Namespace module
+ * @ingroup public
+ * @{
+ */
+
 /** @brief Namespace prefix length, including terminator.
  */
 #define PFX_LEN 8
@@ -86,7 +92,7 @@ LSUP_nsmap_get_ns (const LSUP_NSMap *map, const char *pfx);
  *
  * @param[in] map The map to look up the prefix in.
  *
- * @param[in] pfx The namespace to look up.
+ * @param[in] ns The namespace to look up.
  *
  * @return Found prefix, or NULL if the namespace is not mapped.
  */
@@ -98,7 +104,7 @@ LSUP_nsmap_get_pfx (const LSUP_NSMap *map, const char *ns);
  *
  * @param[in] map Namespace map to look up.
  *
- * @param[in] uri URI string to denormalize.
+ * @param[in] pfx_uri URI string to denormalize.
  *
  * @param[out] fq_uri String pointer to be filled with the FQ URI. If the
  *  namespace is not in the map or an error occurred, this will be NULL. The
@@ -122,7 +128,7 @@ LSUP_nsmap_normalize_uri (
  *
  * @param[in] map Namespace map to look up.
  *
- * @param[in] uri URI string to normalize.
+ * @param[in] fq_uri URI string to normalize.
  *
  * @param[out] pfx_uri String pointer to be filled with the prefixed URI. If
  *  the namespace is not in the map, this will be a duplicate of the original
@@ -147,4 +153,5 @@ LSUP_nsmap_denormalize_uri (
 const char ***
 LSUP_nsmap_dump (const LSUP_NSMap *map);
 
+/// @}  END defgroup namespace
 #endif

+ 11 - 2
include/store.h

@@ -19,6 +19,10 @@
 #include "store_htable.h"
 #include "store_mdb.h"
 
+/** @defgroup store Backend store module
+ * @ingroup public
+ * @{
+ */
 
 /*
  * Define backend types.
@@ -74,7 +78,7 @@ const LSUP_StoreInt *LSUP_store_int (LSUP_StoreType type);
  * The life cycle of a store may normally outspan the one of one or multiple
  * graphs with the same back end, hence it is managed independently.
  *
- * @param store_type[in] Type of store backing the graph. One of the values of
+ * @param[in] store_type Type of store backing the graph. One of the values of
  *  #LSUP_StoreType.
  *
  * @param[in] store_id Identifier for the store. This may be
@@ -130,10 +134,12 @@ LSUP_store_begin (LSUP_Store *store, int flags, void **txn);
  *
  * If the store supports it, commit an open transaction. In case of
  * error, the transaction is left open and it is advisable to roll it back with
- * #LSUP_graph_abort().
+ * #LSUP_store_abort().
  *
  * @param[in] store Store handle.
  *
+ * @param[in] txn transaction handle to commit.
+ *
  * @return LSUP_OK if the transaction was committed successfully; LSUP_NOACTION
  *  if NULL was passed; LSUP_TXN_ERR on error.
  */
@@ -146,8 +152,11 @@ LSUP_store_commit (LSUP_Store *store, void *txn);
  * If the store supports it, abort an open transaction and abandon all changes.
  *
  * @param[in] store Store handle.
+ *
+ * @param[in] txn transaction handle to abort.
  */
 void
 LSUP_store_abort (LSUP_Store *store, void *txn);
 
+/// @}  END store
 #endif  /* LSUP_STORE_H */

+ 64 - 26
include/store_interface.h

@@ -37,6 +37,12 @@
 
 #include "environment.h"
 
+
+/** @defgroup store_interface Store interface module
+ * @ingroup private
+ * @{
+ */
+
 /*
  * Store feature flags.
  *
@@ -69,7 +75,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.
  */
@@ -152,8 +158,6 @@ typedef LSUP_rc (*store_txn_begin_fn_t)(void *store, int flags, void **txn);
  *
  * @param[in] store Store handle.
  *
- * @param[in] txn Transaction handle generated by #store_txn_begin_fn_t.
- *
  * @return LSUP_OK if the transaction was committed successfully, <0 on error.
  */
 typedef LSUP_rc (*store_txn_commit_fn_t)(void *store);
@@ -164,21 +168,42 @@ typedef LSUP_rc (*store_txn_commit_fn_t)(void *store);
  * Only for LSUP_STORE_TXN stores.
  *
  * @param[in] store Store handle.
- *
- * @param[in] txn Transaction handle generated by #store_txn_begin_fn_t.
  */
 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
  * need to be pre-processed, which can be done in the same loop as the next
  * step to keep memory usage low.
  *
- * @param store[in] The store to add to.
+ * @param[in] store The store to add to.
  *
- * @param sc[in] Context as a serialized term. If this is NULL, and the
+ * @param[in] sc Context as a serialized term. If this is NULL, and the
  *  default context is not NULL, triples will be added to the default context
  *  for the store, If the default context for the store is NULL, regardless of
  *  the value of sc, triples will be added with no context. Only meaningful
@@ -195,15 +220,15 @@ typedef void * (*store_add_init_fn_t)(
 
 /** @brief Add one triple into the store.
  *
- * This must be called after #add_init_fn, using the iterator
+ * This must be called after #store_add_init_fn_t, using the iterator
  * yielded by that function. It may be called multiple times and must be
- * followed by #add_done_fn or #add_abort_fn (if supported).
+ * followed by #store_add_done_fn_t or #store_add_abort_fn_t (if supported).
  *
- * @param it[in] Iterator obtained by #store_add_init_fn_t.
+ * @param[in] it Iterator obtained by #store_add_init_fn_t.
  *  The following members are of interest:
  *  it->i stores the total number of records inserted.
  *
- * @param sspo[in] Serialized triple to be added.
+ * @param[in] sspo Serialized triple to be added.
  *
  * @return LSUP_OK if the triple was inserted; LSUP_NOACTION if the triple
  *  already existed; LSUP_DB_ERR if an MDB error occurred.
@@ -214,10 +239,10 @@ typedef LSUP_rc (*store_add_iter_fn_t)(
 
 /** @brief Abort an add loop and free iterator.
  *
- * Usually called on an irrecoverable error from #add_iter_fn. None of the
- * successful inserts in the same loop is retained.
+ * Usually called on an irrecoverable error from #store_add_iter_fn_t. None of
+ * the successful inserts in the same loop is retained.
  *
- * @param it[in] Iterator obtained by #store_add_init_fn_t.
+ * @param[in] it Iterator obtained by #store_add_init_fn_t.
  */
 typedef void (*store_add_abort_fn_t)(void *it);
 
@@ -243,9 +268,9 @@ typedef void * (*iter_txn_fn_t)(void *it);
 
 /** @brief Finalize an add loop and free iterator.
  *
- * This must be called after #add_iter_fn.
+ * This must be called after #store_add_iter_fn_t.
  *
- * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+ * @param[in] it Iterator obtained by #store_add_init_fn_t.
  */
 typedef LSUP_rc (*store_add_done_fn_t)(void *it);
 
@@ -255,6 +280,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,8 +322,8 @@ 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
- * the first result which can be retrieved with #iter_next_fn().
+ * #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_t.
  */
 typedef void * (*store_lookup_fn_t)(
         void *store,
@@ -305,11 +333,11 @@ typedef void * (*store_lookup_fn_t)(
 
 /** @brief Prototype: check for existence of a triple (T/F).
  *
- * @param[in] Store to be queried.
+ * @param[in] store Store to be queried.
  *
- * @param[in] spo Triple to look up. All members must not be NULL.
+ * @param[in] sspo Triple to look up. All members must not be NULL.
  *
- * @param[in] c Optional context to look into. It may be NULL. It is
+ * @param[in] sc Optional context to look into. It may be NULL. It is
  *  disregarded by stores without the LSUP_STORE_CTX feature.
  *
  * @return Whether the triple exist in the store (/context).
@@ -321,8 +349,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_t(). 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 +414,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 #store_lookup_fn_t.
  *
  * @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.
@@ -394,7 +422,7 @@ typedef LSUP_NSMap * (*store_nsm_get_fn_t)(void *store);
  * @param[out] ctx If not NULL, it is populated with a NULL-terminated array of
  *  LSUP_Buffer structs, one for each context associated with the matching
  *  triple. These contexts are the same regardless of the context filter used
- *  in the lookup. The array is freed with a simple #free(). This parameter
+ *  in the lookup. The array is freed with a simple `free()`. This parameter
  *  is ignored by implementations without the LSUP_STORE_CTX feature.
  *
  *  To iterate over the context array, use this loop:
@@ -412,7 +440,7 @@ typedef LSUP_rc (*iter_next_fn_t)(
 
 /** @brief Prototype: free an iterator allocated by a lookup.
  *
- * @param it[in] Iterator pointer. It will be set to NULL after freeing.
+ * @param[in] it Iterator pointer. It will be set to NULL after freeing.
  */
 typedef void (*iter_free_fn_t)(void * it);
 
@@ -455,6 +483,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 +551,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,
@@ -533,4 +570,5 @@ const LSUP_StoreInt my_store_int = {
 };
 */
 
+/// @}  END defgroup store_interface
 #endif  /* _LSUP_STORE_INTERFACE_H */

+ 38 - 32
include/term.h

@@ -6,6 +6,11 @@
 #include "buffer.h"
 #include "namespace.h"
 
+/** @defgroup term RDF term and triple module
+ * @ingroup public
+ * @{
+ */
+
 #define UUID4_URN_SIZE UUIDSTR_SIZE + 10
 
 // Some common RDF term values.
@@ -36,12 +41,10 @@ typedef enum {
     LSUP_TERM_BNODE,        ///< Blank node.
 } LSUP_TermType;
 
-/** @brief IRI information.
- *
- * See regex matching group for #LSUP_URI_REGEX_STR for more information.
- */
+/// Opaque IRI information.
 typedef struct iri_info_t LSUP_IRIInfo;
 
+/// Opaque iterator for link maps.
 typedef struct link_map_iter LSUP_LinkMapIterator;
 
 /// RDF term.
@@ -150,13 +153,13 @@ extern LSUP_TermSet *LSUP_term_cache;
  * such as #LSUP_iriref_new(), #LSUP_literal_new(), etc. as they have strict
  * type checks for the metadata parameter.
  *
- * @param type[in] Term type. One of #LSUP_TermType.
+ * @param[in] type Term type. One of #LSUP_TermType.
  *
- * @param data[in] Term data: textual URI, literal value without data type
+ * @param[in] data Term data: textual URI, literal value without data type
  *  or langtag, etc. It may be NULL for IRI refs and BNodes, in which case a
  *  random identifier is generated.
  *
- * @param metadata[in] Namespace map (LSUP_NSMap *) for IRI refs; language tag
+ * @param[in] metadata Namespace map (LSUP_NSMap *) for IRI refs; language tag
  * (LSUP_LangTag *) for language-tagged literals; or data type (LSUP_Term *)
  * for other literals. It may be NULL.
  *
@@ -176,10 +179,10 @@ LSUP_term_new (LSUP_TermType type, const char *data, void *metadata);
  *
  * Must be freed with #LSUP_term_free.
  *
- * @param data[in] The URI string. If NULL, a UUID4-based URN is generated.
+ * @param[in] data The URI string. If NULL, a UUID4-based URN is generated.
  *  This cannot be NULL if the nsm parameter is not NULL.
  *
- * @param nsm[in] Namespace map. If not NULL, a namespace-prefixed
+ * @param[in] nsm Namespace map. If not NULL, a namespace-prefixed
  *  (#LSUP_TERM_NS_IRIREF) is created, otherwise a regular one
  *  (#LSUP_TERM_IRIREF).
  *
@@ -237,9 +240,9 @@ LSUP_iriref_relative (const LSUP_Term *root, const LSUP_Term *iri);
  *
  * Must be freed with #LSUP_term_free.
  *
- * @param data[in] The literal string.
+ * @param[in] data The literal string.
  *
- * @param datatype[in] Data type URI string. If NULL, the default data type
+ * @param[in] datatype Data type URI string. If NULL, the default data type
  * (xsd:string) is used. The new term takes ownership of the pointer.
  *
  * @return same as #LSUP_term_new().
@@ -253,9 +256,9 @@ LSUP_literal_new (const char *data, LSUP_Term *datatype)
  *
  * Must be freed with #LSUP_term_free.
  *
- * @param data[in] The literal string.
+ * @param[in] data The literal string.
  *
- * @param lang[in] Language tag string.
+ * @param[in] lang Language tag string.
  *
  * @return same as #LSUP_term_new().
  */
@@ -268,7 +271,7 @@ LSUP_lt_literal_new (const char *data, char *lang)
  *
  * Must be freed with #LSUP_term_free.
  *
- * @param data[in] The BNode identifier.
+ * @param[in] data The BNode identifier.
  *
  * @return same as #LSUP_term_new().
  */
@@ -300,7 +303,7 @@ LSUP_term_new_from_buffer (const LSUP_Buffer *sterm);
 
 /** @brief Serialize a term into a buffer.
  *
- * @param[in] sterm Term to convert into a buffer.
+ * @param[in] term Term to convert into a buffer.
  *
  * @return New buffer handle. It must be freed with #LSUP_buffer_free().
  */
@@ -408,10 +411,15 @@ LSUP_triple_serialize (const LSUP_Triple *spo);
 
 /** @brief Initialize internal term pointers in a heap-allocated triple.
  *
- * Terms are NOT copied. To free them with the triple, use #LSUP_triple_free().
- * To only free the triple, use free().
+ * @sa #LSUP_triple_new()
+ *
+ * @param[in] spo Triple pointer to initialize.
  *
- * @param spo[in] Triple pointer to initialize.
+ * @param[in] s Triple subject. It must be an IRIRef or BNode.
+ *
+ * @param[in] p Triple predicate. It must be an IRIRef.
+ *
+ * @param[in] o Triple object.
  */
 LSUP_rc
 LSUP_triple_init (LSUP_Triple *spo, LSUP_Term *s, LSUP_Term *p, LSUP_Term *o);
@@ -419,7 +427,7 @@ LSUP_triple_init (LSUP_Triple *spo, LSUP_Term *s, LSUP_Term *p, LSUP_Term *o);
 
 /** @brief Free the internal pointers of a triple.
  *
- * @param spo[in] Triple to be freed.
+ * @param[in] spo Triple to be freed.
  */
 void
 LSUP_triple_done (LSUP_Triple *spo);
@@ -430,7 +438,7 @@ LSUP_triple_done (LSUP_Triple *spo);
  * NOTE: If the term pointers are not to be freed (e.g. they are owned by a
  * back end), use a simple free(spo) instead of this.
  *
- * @param spo[in] Triple to be freed.
+ * @param[in] spo Triple to be freed.
  */
 void
 LSUP_triple_free (LSUP_Triple *spo);
@@ -440,9 +448,9 @@ LSUP_triple_free (LSUP_Triple *spo);
  *
  * Useful for looping over all terms.
  *
- * @param trp[in] Triple pointer.
+ * @param[in] trp Triple pointer.
  *
- * @param n[in] A number between 0÷2.
+ * @param[in] n A number between 0÷2.
  *
  * @return Corresponding triple term or NULL if n is out of range.
  */
@@ -493,7 +501,7 @@ LSUP_term_set_free (LSUP_TermSet *ts);
  * term's handle is made available in the `existing` variable. In this case,
  * the caller may want to free the passed term which has not been added.
  *
- * @param[in] tl Term set to be added to.
+ * @param[in] ts Term set to be added to.
  *
  * @param[in] term Term to be added to the list. The term set will take
  * ownership of the term and free it when it's freed with
@@ -543,8 +551,7 @@ LSUP_term_set_next (LSUP_TermSet *ts, size_t *i, LSUP_Term **term);
  *
  * The initial state of the returned list is: `{t: [NULL], tl: [NULL]}`
  *
- * Predicates and term lists can be added with #LSUP_link_map_add, and terms
- * can be added to a term list with #LSUP_term_list_add.
+ * Terms can be added to a term list with #LSUP_term_set_add().
  *
  * @param[in] type Type of links that the link map shall contain.
  * @sa #LSUP_LinkType
@@ -582,12 +589,12 @@ LSUP_link_map_type (const LSUP_LinkMap *map);
  * In any case, the caller should not directly use the term and term set after
  * passing them to this function.
  *
- * @param[in] cm Link map handle obtained with #LSUP_link_map_new().
+ * @param[in] cmap Link map handle obtained with #LSUP_link_map_new().
  *
- *  @param[in] t Term to be associated with the given object list. The
- *   link map structure takes ownership of the term.
+ * @param[in] term Term to be associated with the given object list. The
+ *  link map structure takes ownership of the term.
  *
- * @param[in] ts term set to be associated with the given term. The link
+ * @param[in] tset term set to be associated with the given term. The link
  *  list structire takes ownership of the term set and the terms in it.
  *
  * @return LSUP_OK on success; LSUP_MEM_ERR on allocation error.
@@ -638,9 +645,7 @@ LSUP_link_map_next (
  * @param[in] it Link map iterator handle, obtained with
  *  #LSUP_link_map_iter_new().
  *
- * @param[in] term Term to relate to the link map.
- *
- * @param[in|out] spo Result triple. The triple handle must be pre-allocated
+ * @param[in,out] spo Result triple. The triple handle must be pre-allocated
  *  (it may be TRP_DUMMY) and calls to this function will be set its memebers
  *  to term handles owned by the link map. If rc != LSUP_OK, the contents are
  *  undefined.
@@ -652,4 +657,5 @@ LSUP_rc
 LSUP_link_map_triples (
         LSUP_LinkMapIterator *it, LSUP_Triple *spo);
 
+///@} END defgroup term
 #endif

+ 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;
 }

Some files were not shown because too many files changed in this diff