Browse Source

WIP Implement store interface.

Stefano Cossu 2 years ago
parent
commit
925ea48633
17 changed files with 1102 additions and 1124 deletions
  1. 1 0
      TODO.md
  2. 5 3
      include/buffer.h
  3. 44 16
      include/core.h
  4. 0 2
      include/data/bootstrap.h
  5. 46 10
      include/environment.h
  6. 46 60
      include/graph.h
  7. 37 385
      include/store.h
  8. 435 0
      include/store_base.h
  9. 3 192
      include/store_htable.h
  10. 4 8
      include/store_mdb.h
  11. 2 2
      src/codec_nt.c
  12. 1 1
      src/core.c
  13. 68 42
      src/environment.c
  14. 217 216
      src/graph.c
  15. 128 144
      src/store_htable.c
  16. 64 42
      src/store_mdb.c
  17. 1 1
      test/test_store_ht.c

+ 1 - 0
TODO.md

@@ -22,6 +22,7 @@
     - *D* Subclass term types
 - *D* Namespaced IRIs
 - *D* Relative IRIs
+- W Flexible store interface
 - *P* Transaction control
 - *P* Turtle serialization / deserialization
 - *P* Full UTF-8 support

+ 5 - 3
include/buffer.h

@@ -3,8 +3,10 @@
 
 #include "core.h"
 
-// "NULL" key, a value that is never user-provided. Used to mark special
-// values (e.g. deleted records).
+/** @brief "NULL" key, a value that is never user-provided.
+ *
+ * Used to mark special values (e.g. deleted records).
+ */
 #define NULL_KEY 0
 
 /** @brief General-purpose data buffer.
@@ -249,6 +251,6 @@ LSUP_btriple_hash (const LSUP_BufferTriple *strp)
 }
 
 
-#define STRP_DUMMY LSUP_btriple_new (NULL, NULL, NULL)
+#define BTRP_DUMMY LSUP_btriple_new (NULL, NULL, NULL)
 
 #endif

+ 44 - 16
include/core.h

@@ -34,6 +34,8 @@
 #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"
 
@@ -175,10 +177,6 @@ extern char *warning_msg[], *error_msg[];
 
 extern char *LSUP_root_path;
 
-/** @brief Whether the environment is already initialized.
- */
-extern bool LSUP_env_is_init;
-
 
 typedef XXH32_hash_t LSUP_Hash32;
 typedef XXH64_hash_t LSUP_Hash64;
@@ -222,12 +220,13 @@ const char *
 LSUP_strerror (LSUP_rc rc);
 
 
-/**
- * Encode a code point using UTF-8
+/** @brief Encode a code point using UTF-8.
+ *
+ * https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
  *
  * @author Ondřej Hruška <ondra@ondrovo.com>
+ *
  * @license MIT
- * https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
  *
  * @param out - output buffer (min 5 characters), will be 0-terminated
  * @param utf - code point 0-0x10FFFF
@@ -277,23 +276,52 @@ inline int utf8_encode(const uint32_t utf, unsigned char *out)
 }
 
 
-// Error handling via goto.
-#define CHECK(exp, rc, marker) (rc) = (exp); if ((rc) != LSUP_OK) goto marker
+/** @brief Log an error or warning for return codes that are not LSUP_OK.
+ *
+ * Note that, if used outside of the other macros below, care must be taken
+ * to pass it an actual return code rather than an expression, otherwise the
+ * expression will be evaluated multiple times.
+ */
+#define LOG_RC(rc) do {                                             \
+    if ((rc) < 0) log_error (LSUP_strerror (rc));                   \
+    else if ((rc) > 0) log_warn (LSUP_strerror (rc));               \
+} while (0);
+
+/// Error handling via goto.
+#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).
-#define PCHECK(exp, rc, marker) (rc) = (exp); if ((rc) < LSUP_OK) goto marker
+/// Jump if rc is negative (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 is negative (=error)
-#define RCCK(exp) LSUP_rc _rc = (exp); if (UNLIKELY (_rc < 0)) return _rc
+/// Return rc if it is of LSUP_rc type and is negative (=error)
+#define RCCK(exp) do {                                              \
+    LSUP_rc _rc = (exp);                                            \
+    LOG_RC(_rc);                                                    \
+    if (UNLIKELY (_rc < LSUP_OK)) return _rc;                       \
+} while (0);
 
-// Return NULL if it is of LSUP_rc type and is negative (=error)
-#define RCNL(exp) if (UNLIKELY ((exp) < 0)) return NULL
+/// Return NULL if it is of LSUP_rc type and is negative (=error)
+#define RCNL(exp) do {                                              \
+    LSUP_rc _rc = (exp);                                            \
+    LOG_RC(_rc);                                                    \
+    if (UNLIKELY (_rc < LSUP_OK)) return NULL;                      \
+} while (0);
 
+/// Allocate one pointer with malloc and return rc if it fails.
 #define MALLOC_GUARD(var, rc) do {                                  \
     (var) = malloc (sizeof *(var));                                 \
     if (UNLIKELY (var == NULL)) return (rc);                        \
 } while (0);
 
+/// Allocate one pointer with calloc and return rc if it fails.
 #define CALLOC_GUARD(var, rc) do {                                  \
     (var) = calloc (1, sizeof *(var));                              \
     if (UNLIKELY (var == NULL)) return (rc);                        \
@@ -306,4 +334,4 @@ inline int utf8_encode(const uint32_t utf, unsigned char *out)
 #define CALLOC_GUARD_NL(var) CALLOC_GUARD((var), NULL)              \
 */
 
-#endif
+#endif  /* _LSUP_CORE_H */

+ 0 - 2
include/data/bootstrap.h

@@ -1,8 +1,6 @@
 #ifndef LSUP_INIT_DATA_H
 #define LSUP_INIT_DATA_H
 
-#define LSUP_NS "urn:lsup:"
-
 /** @brief Initial namespace map.
  */
 const char *init_nsmap[][2] = {

+ 46 - 10
include/environment.h

@@ -1,11 +1,24 @@
 /** @file environment.h
  *
  * @brief Handle LSUP environment initialization and teardown.
+ *
+ * #LSUP_init() should be called before performing any other interaction with
+ * this library.
+ *
+ * #LSUP_done() is called automatically on program exit (`atexit`).
  */
 
 #ifndef _LSUP_ENVIRONMENT_H
 #define _LSUP_ENVIRONMENT_H
 
+
+#include "term.h"
+
+#if 0
+/*
+ * TODO Deprecating all non-default environments. All is needed is a NSMap
+ * which can be created separately for in-memory graphs.
+ */
 #include "store_mdb.h"
 
 
@@ -13,13 +26,14 @@
  * Data types.
  */
 
-/** @brief Environment structure containing "global" LSUP variables.
+/** @brief Environment structure containing global LSUP variables.
  *
  * Multiple environments may be opened within one program. One environment,
- * the default one, must always be opened before performing any operation with
- * the library.
+ * the default one, is always opened when calling #LSUP_init(), before
+ * performing any operation with the library.
  *
  * The namespace map is used by all volatile (not LSUP_STORE_PERM) stores
+ * associated with the environment.
  */
 typedef struct env_t {
     LSUP_Buffer *       default_ctx;            ///> Default context URI.
@@ -38,9 +52,12 @@ extern LSUP_Env *LSUP_default_env;
  * will be wiped clean the next time this function is called.
  */
 LSUP_Env *
-LSUP_env_new (
-        const char *default_ctx, const char *mdb_path,
-        const LSUP_NSMap *nsmap);
+LSUP_env_new (const char *default_ctx, const LSUP_NSMap *nsmap);
+#endif
+
+extern LSUP_NSMap *LSUP_default_nsm; /// Default namespace prefix map.
+extern LSUP_Term *LSUP_default_ctx; /// Default context.
+extern LSUP_Buffer *LSUP_default_ctx_buf; /// Serialized default context.
 
 
 /** @brief Initialize the default environment.
@@ -57,6 +74,17 @@ LSUP_rc
 LSUP_init (void);
 
 
+/** @brief Whether the environment is already initialized.
+ *
+ * @TODO Check if the default NS was inserted; this would be slower but more
+ * accurate.
+ */
+inline bool
+LSUP_is_init (void)
+{ return LSUP_term_cache != NULL; }
+
+
+#if 0
 /** @brief Close an environment.
  *
  * This only needs to be done for non-default environments. The environment
@@ -64,18 +92,26 @@ LSUP_init (void);
  */
 void
 LSUP_env_free (LSUP_Env *env);
+#endif 
 
-/** @brief Close the defailt environment.
+/** @brief Close the default environment.
  *
  * This is called by atexit(). If called before then, subsequent calls have
  * no effect.
  */
-void LSUP_done (void);
+void
+LSUP_done (void);
 
+
+/** TODO
+ */
 LSUP_rc
-LSUP_env_push_id (LSUP_Env *env, const uint32_t key, const char *data);
+LSUP_env_put_id (const uint32_t key, const char *data);
 
+
+/** TODO
+ */
 const char *
-LSUP_env_get_id (LSUP_Env *env, const uint32_t key);
+LSUP_env_get_id (const uint32_t key);
 
 #endif /* _LSUP_ENVIRONMENT_H */

+ 46 - 60
include/graph.h

@@ -1,34 +1,14 @@
 #ifndef _LSUP_GRAPH_H
 #define _LSUP_GRAPH_H
 
+#include "store.h"
 #include "environment.h"
 #include "term.h"
 
-/*
- * Define backend types.
- *
- * Add new store implementations to this table.
- */
-
-#define BACKEND_TBL                                                           \
-/*          #enum pfx   #store          #iterator   */\
-    /*ENTRY(  HTABLE,     htstore_int,    htiter_int )*/  \
-    ENTRY(  MDB,        mdbstore_int,   mdbiter_int ) \
-    ENTRY(  MDB_TMP,    mdbstore_int,   mdbiter_int ) \
-
-
-/** @brief Store types. All prefixed with `LSUP_STORE_`.
- */
-typedef enum {
-#define ENTRY(a, b, c) LSUP_STORE_##a,
-    BACKEND_TBL
-#undef ENTRY
-} LSUP_store_type;
-
 
 /** @brief Graph object.
  */
-typedef struct Graph LSUP_Graph;
+typedef struct graph_t LSUP_Graph;
 
 /** @brief Graph iterator.
  *
@@ -36,60 +16,52 @@ typedef struct Graph LSUP_Graph;
  * over lookup results with #LSUP_graph_iter_next. It must be freed with
  * #LSUP_graph_iter_free when done.
  */
-typedef struct GraphIterator LSUP_GraphIterator;
+typedef struct graph_iter_t LSUP_GraphIterator;
 
 
 /** @brief Create an empty graph.
  *
- * The new graph has zero capacity and a random URN. To change either one, use
- * #LSUP_graph_resize and #LSUP_graph_set_uri, respectively.
+ * @param[in] uri URI of the new graph. If NULL, a UUID4 URN is generated.
  *
- * The graph is assigned a default (volatile) namespace map if it's in memory,
- * hence all graphs share the same namespace map by default. To change this,
- * use #LSUP_graph_set_namespace(). MDB graphs use a persistent namespace map
- * that is common to all the graphs in the same store. This cannot be changed.
+ * @param store_type[in] Type of store backing the graph. One of the values of
+ *  #LSUP_StoreType.
  *
- * @param store_type[in] Type of store for the graph. One of the values of
- *  #LSUP_store_type.
+ * @param[in] store_id Identifier for the back end store. This may be
+ *  interpreted differently by each store implementation. For the MDB store,
+ *  this is the file path where the store is located. It is ignored by volatile
+ *  stores (with LSUP_STORE_PERM feature flag set to false). If a store
+ *  does not exist for the given identifier, a new one is initialized. If this
+ *  parameter is NULL, the default store for the selected type is used.
  *
- * @return LSUP_OK if the graph was created, or < 0 if an error occurred.
- */
-LSUP_Graph *
-LSUP_graph_new_env (
-        const LSUP_Env *env, LSUP_Term *uri, const LSUP_store_type store_type);
-
-
-/** @brief Create an empty graph with the default environment.
+ * @param[in] nsm Namespace map to use for an in-memory graph. This is ignored
+ *  by graphs backed by permanent stores, which handle their own namespace map.
+ *  If this is NULL, the graph is assigned a global namespace map that lives
+ *  until #LSUP_done() is called.
  *
- * This is likely to be used more often than #LSUP_graph_new_env().
+ * @return New graph, or NULL on error. Must be freed with #LSUP_graph_free().
  */
-#define LSUP_graph_new(uri, type) \
-    LSUP_graph_new_env (LSUP_default_env, uri, type)
-
-
-/** @brief Shortcut for #LSUP_graph_new_lookup_env() on default MDB store.
- */
-#define LSUP_graph_new_lookup(s, p, o) \
-    LSUP_graph_new_lookup_env (LSUP_default_env, (s), (p), (o))
+LSUP_Graph *
+LSUP_graph_new (
+        LSUP_Term *uri, const LSUP_StoreType store_type, const char *store_id,
+        LSUP_NSMap *nsm, size_t size);
 
 
 /** @brief copy a graph into a new one.
  *
  * The new graph is compacted to the minimum required size.
  *
- * src[in] Graph to be copied.
- *
- * @param uri URI of the destination graph. If NULL, a UUID4 URN is generated.
+ * @param[in] src Graph to be copied.
  *
- * @param gr[out] Pointer to a pointer to the destination graph. It must be
- *  freed with #LSUP_graph_free when done.
+ * @param[in] uri URI of the destination graph. If NULL, a UUID4 URN is
+ *  generated.
  *
- * @return LSUP_OK if the graph was copied, or < 0 if an error occurred.
+ * @return New graph, or NULL on error. Must be freed with #LSUP_graph_free().
  */
 LSUP_Graph *
-LSUP_graph_copy (const LSUP_Graph *src);
+LSUP_graph_copy (const LSUP_Graph *src, LSUP_Term *uri);
 
 
+#if 0
 /** @brief Copy the contents of a graph into a permanent store.
  *
  * It is possible to store a memory graph, a RAMdisk MDB graph, or a
@@ -114,11 +86,21 @@ LSUP_graph_copy (const LSUP_Graph *src);
 LSUP_rc
 LSUP_graph_store (
         const LSUP_Graph *src, LSUP_Graph **dest_p, const LSUP_Env *env);
+#endif
+
+/** @brief Copy triples from a source graph into a destination one.
+ *
+ * The destination graph is not initialized here, so the copy is cumulative.
+ */
+LSUP_rc
+LSUP_graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest);
 
 
 /** Perform a boolean operation between two graphs.
  *
- * This method yields a new graph as the result of the operation.
+ * This method populates an initialized graph with the result of the operation
+ * between two other graphs. The resulting graph may be of any store type and
+ * may be the result of graphs of different store types.
  *
  * @param op[in] Operation to perform. One of #LSUP_bool_op.
  *
@@ -126,12 +108,16 @@ LSUP_graph_store (
  *
  * @param gr2[in] Second operand.
  *
- * @param res[out] Result graph. It must be freed with #LSUP_graph_free when
- *  done.
+ * @param res[out] Result graph. The handle should be initialized via
+ *  #LSUP_graph_new(). Any preexisting contents are not removed. If an
+ *  unrecoverable error occurs, this graph is freed.
+ *
+ * @return LSUP_OK on success; <0 on error.
  */
-LSUP_Graph *
+LSUP_rc
 LSUP_graph_bool_op(
-        const LSUP_bool_op op, const LSUP_Graph *gr1, const LSUP_Graph *gr2);
+        const LSUP_bool_op op, const LSUP_Graph *gr1, const LSUP_Graph *gr2,
+        LSUP_Graph *res);
 
 
 /** @brief Free a graph.

+ 37 - 385
include/store.h

@@ -1,411 +1,63 @@
 /** @file store.h
  *
- * @brief Store back end interfaces.
+ * @brief Generic store dependency.
  *
- * The basic interfaces for store and store iterator implementations are
- * defined here. New store implementations should include this header and
- * implement four basic elements:
+ * This code allows to use the store interface with any supported back end
+ * without any knowledge of the back end implementations. Code using the store
+ * interface need only include this file.
  *
- * - A structure representing the store back end and one for the iterator.
- *   These structures will be opaque to all external modules and their layouts
- *   are entirely up to the implementer.
- *
- * - The LSUP_StoreInt and LSUP_StoreIteratorInt interfaces as well as the
- *   functions defined in the interfaces necessary to interact with the store.
- *
- * See the `store_htable.{c,h}` and `store_mdb.{c,h}` files for examples of
- * fully functioning implementations.
- *
- * The #LSUP_Store structure defines a store back end for raw buffer triples.
- * Nothing in the store should hint at RDF triples—it should accept and output
- * exclusively raw byte buffers (LSUP_Buffer). A store may have any of the
- * `LSUP_STORE_*` faeture flags which should be reflected by how its members
- * are implemented.
- *
- * The #LSUP_StoreIterator structure defines a store iterator implementation
- * that is designed to work with the store implementation. It must define a
- * "next" function for retrieving a triple in a lookup and a "free" function
- * to deallocate the iterator after use. Functions for creating iterators for
- * lookup and incremental addition are defined in the associated store.
+ * New store implementations should be added to this file as `#include`s as
+ * well as entries in the `BACKEND_TBL` macro.
  */
 
-
 #ifndef _LSUP_STORE_H
 #define _LSUP_STORE_H
 
-#include "namespace.h"
-#include "data/bootstrap.h"
-
-
 /*
- * Store feature flags.
- *
- * NOTE: LSUP_STORE_PERM need only be set by an implementation based on whether
- * its path is on a default temporary dir (e.g. LSUP_MDB_RAMDISK_PATH). If this
- * flag is not set, it means the data will be cleared before the next execution
- * of the program. However, its being set does not guarantee the persistence of
- * the medium (i.e. a "permanent" store may have been created ad hoc on a
- * tempfs).
+ * Add new back end headers here.
  */
-#define LSUP_STORE_PERM     1<<0    /// Store is on a permanent location.
-#define LSUP_STORE_CTX      1<<1    /// Store supports contexts (quads).
-#define LSUP_STORE_IDX      1<<2    /// Store is fully SPO(C)-indexed.
-#define LSUP_STORE_TXN      1<<3    /// Supports manual transaction handling.
-#define LSUP_STORE_NET      1<<4    /// Store is over a network protocol.
+#include "store_htable.h"
+#include "store_mdb.h"
 
 
 /*
- * Iterator function types.
- */
-
-/** @brief Prototype: yield the matching triples and advance the iterator.
- *
- * NOTE: Iterators keep transactions open. Don't hold on to them longer than
- * necessary.
- *
- * NOTE: The memory pointed to by the individual LSUP_Buffer pointers is
- * owned by the database. It must not be written to or freed. To modify
- * the data or use them beyond the caller's scope, this memory must be copied.
- *
- * @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, NULL if not found. Internal callers (e.g. counters) may pass
- * NULL if they don't need the serialized triples.
- *
- * @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
- *  is ignored by implementations without the LSUP_STORE_CTX feature.
- *
- *  To iterate over the context array, use this loop:
- *
- *      size_t i = 0;
- *      while (ctx[i].addr)
- *          do_something(ctx + i++); // Buffer data are RO.
- *
- * @return LSUP_OK if results were found; LSUP_END if no (more) results were
- * found; LSUP_DB_ERR if a backend error occurred.
- */
-typedef LSUP_rc (*iter_next_fn_t)(
-        void *it, LSUP_BufferTriple *sspo, LSUP_Buffer **ctx);
-
-
-/** @brief Prototype: free an iterator allocated by a lookup.
- *
- * @param it[in] Iterator pointer. It will be set to NULL after freeing.
- */
-typedef void (*iter_free_fn_t)(void * it);
-
-
-/*
- * Store function types.
- */
-
-/** @brief Prototype: create any environment necessary for the store to work.
- *
- * @param[in] clear Whether to remove a previous environment at this location.
- *
- * @param[in,out] path Path of the suggested directory to use. It may be NULL,
- *  in which case it will be set either to the environment variable
- *  LSUP_STORE_PATH, or if that is not set, a default local path.
- */
-typedef LSUP_rc (*store_setup_fn_t)(const char *path, bool clear);
-
-
-/** @brief Prototype: create a new store.
- *
- * @param[in] Location for the new store. This depends on the implementation,
- *  which may ignore it (e.g. an in-memory store). Implementations should
- *  provide instructions on how to use and interpret this parameter.
- *
- * @param[in] default_ctx Default context IRI (serialized). It should only be
- *  considered by implementations with the LSUP_STORE_CTX feature.
- *
- * @return New store handle.
- */
-typedef void * (*store_new_fn_t)(
-        const char *location, const LSUP_Buffer *default_ctx);
-
-
-/** @brief Prototype: free store handle.
- *
- * @param[in] store Store handle.
- *
- */
-typedef void (*store_free_fn_t)(void *store);
-
-
-/** @brief Prototype: get store size.
- *
- * @param store[in] The store to calculate size of.
- *
- * @return Number of stored SPO triples (across all contexts if supported).
- */
-typedef size_t (*store_size_fn_t)(void *store);
-
-
-/** @brief Print stats about a store.
- *
- * TODO
- *
- * @param store[in] The store to get stats for.
- */
-/* TODO
-typedef LSUP_rc (*store_stat_fn_t)(void *store, void *stat);
-*/
-
-
-/** @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 sc[in] 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
- *  for stores with the LSUP_STORE_CTX feature.
- *
- * @return Iterator handle to be passed to the following load steps.
- */
-typedef void * (*store_add_init_fn_t)(void *store, const LSUP_Buffer * sc);
-
-
-/** @brief Add one triple into the store.
- *
- * This must be called after #add_init_fn, 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).
- *
- * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
- *  The following members are of interest:
- *  it->i stores the total number of records inserted.
- *
- * @param sspo[in] 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.
- */
-typedef LSUP_rc (*store_add_iter_fn_t)(void *it, const LSUP_BufferTriple * sspo);
-
-
-/** @brief Abort an add loop and free iterator.
+ * Define backend types.
  *
- * Usually called on an irrecoverable error from #add_iter_fn. None of the
- * successful inserts in the same loop is retained.
- *
- * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+ * Add new store implementations to this table.
  */
-typedef void (*store_add_abort_fn_t)(void *it);
+#define BACKEND_TBL                                                           \
+/*          #enum pfx   #store if       */\
+    ENTRY(  HTABLE,     htstore_int  )  \
+    ENTRY(  MDB,        mdbstore_int ) \
 
 
-/** @brief Finalize an add loop and free iterator.
- *
- * This must be called after #add_iter_fn.
- *
- * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+/** @brief Store types. All prefixed with `LSUP_STORE_`.
  */
-typedef LSUP_rc (*store_add_done_fn_t)(void *it);
+typedef enum {
+#define ENTRY(a, b) LSUP_STORE_##a,
+    BACKEND_TBL
+#undef ENTRY
+} LSUP_StoreType;
 
 
-/** @brief Add a single term to the store.
- *
- * @param[in] store Store handle.
- *
- * @param[in] sterm Serialized term to store.
- */
-typedef LSUP_rc (*store_add_term_fn_t)(void *store, const LSUP_Buffer *sterm);
-
-
-/** @brief Prototype: look up triples by pattern matching.
- *
- * This function may return a count of matches and/or an iterator of results as
- * serialized triples.
- *
- * Any and all of the terms may be NULL, which indicates an unbound query
- * term. Stores with context not set or witout context support will always
- * ignore the fourth term.
- *
- * @param[in] store The store to be queried.
- *
- * @param[in] ss Buffer representing the serialized s term.
- *
- * @param[in] sp Buffer representing the serialized p term.
- *
- * @param[in] so Buffer representing the serialized o term.
+/** @brief Store structure.
  *
- * @param[in] sc Serialized context to limit search to. It may be NULL, in
- * which case search is done in all contexts. Note that triples inserted
- * without context are assigned the *default* context for the store.
+ * Code using the store interface should create an instance of this structure
+ * with pointers to the store interface of their choice, and to an opaque
+ * structure to hold the store state.
  *
- * @param[out] ct If not NULL, this will be populated with the number of
- *  entries found. It is very inexpensive to set for lookups without context,
- *  much less so for 1-bound and 2-bound context lookups, in which cases it
- *  should be set only if needed.
+ * Iterator state handles generated by lookup and add functions are kept
+ * outside of this structure, but manipulated by the interface specified in
+ * here.
  *
- * @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().
+ * @sa #LSUP_graph_new()
  */
-typedef void * (*store_lookup_fn_t)(
-        void *store,
-        const LSUP_Buffer *ss, const LSUP_Buffer *sp, const LSUP_Buffer *so,
-        const LSUP_Buffer *sc, size_t *ct);
-
-
-/** @brief Prototype: delete triples by pattern matching.
- *
- * The ss, sp, so, sc terms act as a matching pattern as documented in
- * #store_lookup_fn. if not NULL, ct yields the number of triples actually
- * deleted.
- */
-typedef LSUP_rc (*store_remove_fn_t)(
-        void *store,
-        const LSUP_Buffer *ss, const LSUP_Buffer *sp, const LSUP_Buffer *so,
-        const LSUP_Buffer *sc, size_t *ct);
-
-
-/** @brief Put an in-memory namespace map into a permanent back end.
- *
- * This is only available in stores with the LSUP_STORE_PERM feature.
- *
- * Existing prefixes and namespaces are not updated. Thus, if the following are
- * already stored:
- *
- * ns1: <urn:ns:a#>
- * ns2: <urn:ns:b#>
- *
- * Neither of the following will be inserted:
- *
- * ns3: <urn:ns:a#>
- * ns2: <urn:ns:c#>
- *
- * @param[in] store MDB store to update.
- *
- * @param[out] nsm Namespace map handle to store.
- *
- * @return LSUP_OK if all terms were updated; LSUP_CONFLICT if one or more
- *  namespaces or terms were not updated because they already existed; <0 if
- *  an error occurred.
- */
-typedef LSUP_rc (*store_nsm_put_fn_t)(void *store, const LSUP_NSMap * nsm);
-
-
-/** @brief Get the store's namespace prefix map.
- *
- * @param[in] store MDB store to query.
- *
- * @return NS map or NULL on error.
- */
-typedef LSUP_NSMap * (*store_nsm_get_fn_t)(void *store);
-
-
-/*
- * Iterface type definitions.
- */
-
-/** @brief Store interface.
- */
-typedef struct store_t {
-    // Basic properties.
-    char                name[16];       ///> Store type name.
-    int                 features;       ///> Feature flags.
-
-    // Allocation, setup and deallocation.
-    store_setup_fn_t    setup_fn;       ///> Optional function called before
-                                        ///>    new_fn.
-    store_new_fn_t      new_fn;         ///> Create a new store instance.
-    store_free_fn_t     free_fn;        ///> Free the store.
-
-    // Metadata.
-    store_size_fn_t     size_fn;        ///> Number of triples in the store.
-    //store_stat_fn       stat_fn;        ///> Store statistics. Optional.
-
-    // Addition.
-    store_add_init_fn_t add_init_fn;    ///> Initialize add iteration.
-    store_add_iter_fn_t add_iter_fn;    ///> Add one triple.
-    store_add_abort_fn_t add_abort_fn;  ///> Abort (roll back) the add process.
-                                        ///>    Only available in stores with
-                                        ///>    LSUP_STORE_TXN feature.
-                                        ///>    Optional.
-    store_add_done_fn_t add_done_fn;    ///> Complete the add process.
-    store_add_term_fn_t add_term_fn;    ///> Add (index) a term to the store.
-                                        ///>    Only available in stores with
-                                        ///>    LSUP_STORE_IDX feature.
-                                        ///>    Optional.
-
-    // Look up.
-    store_lookup_fn_t   lookup_fn;      ///> Look up triples by pattern.
-
-    // Removal.
-    store_remove_fn_t   remove_fn;      ///> Remove triples by pattern.
-
-    // Namespace prefix mapping.
-    store_nsm_put_fn_t  nsm_put_fn;     ///> Add a namespace/prefix pair to
-                                        ///>    the prefix map.
-                                        ///>    Only available (and mandatory)
-                                        ///>    in stores with the
-                                        ///>    LSUP_STORE_IDX feature.
-    store_nsm_get_fn_t  nsm_get_fn;     ///> Get a namespace/prefix from
-                                        ///>    the prefix map.
-                                        ///>    Only available (and mandatory)
-                                        ///>    in stores with the
-                                        ///>    LSUP_STORE_IDX feature.
-} LSUP_StoreInt;
-
-
-/** @brief Store iterator interface.
- */
-typedef struct store_it_t {
-    char                name[16];       ///> Iterator type name.
-
-    iter_next_fn_t      iter_fn;        ///> Advance to next step and yield
-                                        ///>    one triple key if found.
-    iter_free_fn_t      free_fn;        ///> Free the iterator.
-} LSUP_StoreIteratorInt;
-
-
-/*
- * Template for a new store and iterator implementation.
- * These should be placed in the .c file where the interface functions are
- * defined, and declared as `extern` in the related .h file.
-
-const LSUP_StoreInt my_store_int = {
-    .name           = "My Store", // Truncated to 15 chars.
-    .features       = LSUP_STORE_PERM | LSUP_STORE_IDX,
-
-    .setup_fn       = my_setup_fn,
-    .new_fn         = my_new_fn,
-    .free_fn        = my_free_fn,
-
-    .size_fn        = my_size_fn,
-
-    .add_init_fn    = my_init_fn,
-    .add_iter_fn    = my_iter_fn,
-    .add_abort_fn   = my_add_abort_fn,
-    .add_done_fn    = my_add_done_fn,
-    .add_term_fn    = my_add_term_fn,
-
-    .lookup_fn      = my_lookup_fn,
-
-    .remove_fn      = my_remove_fn,
-
-    .nsm_put_fn     = my_nsm_put_fn,
-    .nsm_get_fn     = my_nsm_get_fn,
-};
-
-const LSUP_StoreIteratorInt my_it_int = {
-    .name           = "My Iterator",
-
-    .iter_next_fn_t = my_iter_next_fn,
-    .iter_free_fn_t = my_iter_free_fn,
-};
-*/
+typedef struct store_it {
+    LSUP_StoreType                  type;   ///< Store type.
+    char *                          id;     /**< Store ID. NOTE: This is
+                                               * NULL for volatile stores. */
+    const LSUP_StoreInt *           sif;    ///< Store interface.
+    void *                          data;   ///< Store back end data.
+} LSUP_Store;
 
-#endif  /* _LSUP_STORE_H */
+#endif  /* LSUP_STORE_H */

+ 435 - 0
include/store_base.h

@@ -0,0 +1,435 @@
+/** @file store.h
+ *
+ * @brief Common store back end interfaces.
+ *
+ * Code using the store interface should include NOT this header, but rahter
+ * `store.h`.
+ *
+ * This header is included by all back end implementations, which are in
+ * their turn included by `store.h`.
+ *
+ * The basic interfaces for store and store iterator implementations are
+ * defined here. New store implementations should include this header and
+ * implement three basic elements:
+ *
+ * - A structure representing the store back end. This structure will be
+ *   opaque to all downstream code and its layout is entirely up to the
+ *   implementer.
+ *
+ * - A structure representing a store iterator state, also opaque.
+ *
+ * - The LSUP_StoreInt interface with all the functions defined in the
+ *   interface necessary to interact with the store.
+ *
+ * See the `store_htable.{c,h}` and `store_mdb.{c,h}` files for examples of
+ * fully functioning implementations.
+ *
+ * The #LSUP_StoreInt structure defines a store interface for raw buffer
+ * triples. Nothing in the store functions' signatures should hint at RDF
+ * triples—they should accept and produce exclusively raw byte buffers
+ * (#LSUP_Buffer). A store interface may have any of the `LSUP_STORE_*` faeture
+ * flags which should be reflected in the way its members are implemented.
+ */
+
+
+#ifndef _LSUP_STORE_BASE_H
+#define _LSUP_STORE_BASE_H
+
+#include "environment.h"
+
+/*
+ * Store feature flags.
+ *
+ * NOTE: LSUP_STORE_PERM need only be set by an implementation based on whether
+ * its path is on a default temporary dir (e.g. LSUP_MDB_RAMDISK_PATH). If this
+ * flag is not set, it means the data will be cleared before the next execution
+ * of the program. However, its being set does not guarantee the persistence of
+ * the medium (i.e. a "permanent" store may have been created ad hoc on a
+ * tempfs).
+ */
+typedef enum {
+    LSUP_STORE_PERM     = 1<<0,   ///< Store is on a permanent support.
+    LSUP_STORE_CTX      = 1<<1,   ///< Store supports contexts (quads).
+    LSUP_STORE_IDX      = 1<<2,   ///< Store is fully SPO(C)-indexed.
+    LSUP_STORE_TXN      = 1<<3,   ///< Supports transaction handling.
+    LSUP_STORE_COW      = 1<<4,   ///< Copy on write. @sa #iter_next_fn_t()
+    //LSUP_STORE_NET      = 1<<5,   ///< Store is over a network protocol.
+} LSUP_StoreFeature;
+
+
+/*
+ * Store function types.
+ */
+
+/** @brief Prototype: create any environment necessary for the store to work.
+ *
+ * This function should be idempotent on separate calls to the same `id`,
+ * unless the `clear` option is set to `true`.
+ *
+ * @param[in,out] id Identifier to use for the store. This should be
+ * a URI that uniquely identifies a back end for the implementation using it,
+ * 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 .
+ *
+ * @param[in] clear Whether to remove an existing environment with the same ID.
+ */
+typedef LSUP_rc (*store_setup_fn_t)(const char *id, bool clear);
+
+
+/** @brief Prototype: create a new store.
+ *
+ * @param[in] id Identifier for the new store. How this is interpreted, and
+ * whether it is even used, depends on the implementation, which should
+ * provide documentation on how to pass and interpret this parameter.
+ *
+ * @param[in] size Initial size for the store. It may be 0. Only meaningful
+ *  for stores that may preallocate memory, such as #HTStore.
+ *
+ * @return New store handle.
+ */
+typedef void * (*store_new_fn_t)(const char *id, size_t size);
+
+
+/** @brief Prototype: free store handle.
+ *
+ * @param[in] store Store handle.
+ *
+ */
+typedef void (*store_free_fn_t)(void *store);
+
+
+/** @brief Prototype: get the store ID.
+ *
+ * @param store[in] Store handle.
+ *
+ * @return store ID string. This is a copy and should be freed after use.
+ */
+typedef char * (*store_id_fn_t)(const void *store);
+
+
+/** @brief Prototype: get store size.
+ *
+ * @param store[in] The store to calculate size of.
+ *
+ * @return Number of stored SPO triples (across all contexts if supported).
+ */
+typedef size_t (*store_size_fn_t)(const void *store);
+
+
+/** @brief Print stats about a store.
+ *
+ * TODO
+ *
+ * @param store[in] The store to get stats for.
+ */
+/* TODO
+typedef LSUP_rc (*store_stat_fn_t)(void *store, void *stat);
+*/
+
+
+/** @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 sc[in] 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
+ *  for stores with the LSUP_STORE_CTX feature.
+ *
+ * @return Iterator handle to be passed to the following load steps.
+ */
+typedef void * (*store_add_init_fn_t)(void *store, const LSUP_Buffer * sc);
+
+
+/** @brief Add one triple into the store.
+ *
+ * This must be called after #add_init_fn, 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).
+ *
+ * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+ *  The following members are of interest:
+ *  it->i stores the total number of records inserted.
+ *
+ * @param sspo[in] 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.
+ */
+typedef LSUP_rc (*store_add_iter_fn_t)(
+        void *it, const LSUP_BufferTriple * sspo);
+
+
+/** @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.
+ *
+ * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+ */
+typedef void (*store_add_abort_fn_t)(void *it);
+
+
+/** @brief Finalize an add loop and free iterator.
+ *
+ * This must be called after #add_iter_fn.
+ *
+ * @param it[in] Iterator obtained by #LSUP_mdbstore_add_init.
+ */
+typedef LSUP_rc (*store_add_done_fn_t)(void *it);
+
+
+/** @brief Add a single term to the store.
+ *
+ * @param[in] store Store handle.
+ *
+ * @param[in] sterm Serialized term to store.
+ */
+typedef LSUP_rc (*store_add_term_fn_t)(void *store, const LSUP_Buffer *sterm);
+
+
+/** @brief Prototype: look up triples by pattern matching.
+ *
+ * This function may return a count of matches and/or an iterator of results as
+ * serialized triples.
+ *
+ * Any and all of the terms may be NULL, which indicates an unbound query
+ * term. Stores with context not set or witout context support will always
+ * ignore the fourth term.
+ *
+ * @param[in] store The store to be queried.
+ *
+ * @param[in] ss Buffer representing the serialized s term.
+ *
+ * @param[in] sp Buffer representing the serialized p term.
+ *
+ * @param[in] so Buffer representing the serialized o term.
+ *
+ * @param[in] sc Serialized context to limit search to. It may be NULL, in
+ * which case search is done in all contexts. Note that triples inserted
+ * without context are assigned the *default* context for the store.
+ *
+ * @param[out] ct If not NULL, this will be populated with the number of
+ *  entries found. It is very inexpensive to set for lookups without context,
+ *  much less so for 1-bound and 2-bound context lookups, in which cases it
+ *  should be set only if needed.
+ *
+ * @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().
+ */
+typedef void * (*store_lookup_fn_t)(
+        void *store,
+        const LSUP_Buffer *ss, const LSUP_Buffer *sp, const LSUP_Buffer *so,
+        const LSUP_Buffer *sc, size_t *ct);
+
+
+/** @brief Prototype: check for existence of a triple (T/F).
+ *
+ * @param[in] Store to be queried.
+ *
+ * @param[in] spo Triple to look up. All members must not be NULL.
+ *
+ * @param[in] c 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).
+ */
+typedef bool (*store_trp_exist_fn_t)(
+        void *store, const LSUP_BufferTriple *sspo, const LSUP_Buffer *sc);
+
+
+/** @brief Prototype: delete triples by pattern matching.
+ *
+ * The ss, sp, so, sc terms act as a matching pattern as documented in
+ * #store_lookup_fn. if not NULL, ct yields the number of triples actually
+ * deleted.
+ */
+typedef LSUP_rc (*store_remove_fn_t)(
+        void *store,
+        const LSUP_Buffer *ss, const LSUP_Buffer *sp, const LSUP_Buffer *so,
+        const LSUP_Buffer *sc, size_t *ct);
+
+
+/** @brief Put an in-memory namespace map into a permanent back end.
+ *
+ * This is only available in stores with the LSUP_STORE_PERM feature.
+ *
+ * Existing prefixes and namespaces are not updated. Thus, if the following are
+ * already stored:
+ *
+ * ns1: <urn:ns:a#>
+ * ns2: <urn:ns:b#>
+ *
+ * Neither of the following will be inserted:
+ *
+ * ns3: <urn:ns:a#>
+ * ns2: <urn:ns:c#>
+ *
+ * @param[in] store MDB store to update.
+ *
+ * @param[out] nsm Namespace map handle to store.
+ *
+ * @return LSUP_OK if all terms were updated; LSUP_CONFLICT if one or more
+ *  namespaces or terms were not updated because they already existed; <0 if
+ *  an error occurred.
+ */
+typedef LSUP_rc (*store_nsm_put_fn_t)(void *store, const LSUP_NSMap * nsm);
+
+
+/** @brief Get the store's namespace prefix map.
+ *
+ * @param[in] store MDB store to query.
+ *
+ * @return NS map or NULL on error.
+ */
+typedef LSUP_NSMap * (*store_nsm_get_fn_t)(void *store);
+
+
+/*
+ * Iterator function types.
+ */
+
+/** @brief Prototype: yield the matching triples and advance the iterator.
+ *
+ * NOTE: Iterators keep transactions open. Don't hold on to them longer than
+ * necessary.
+ *
+ * NOTE: If the store interface has the LSUP_STORE_COW feature, the memory
+ * buffer referenced by the #LSUP_Buffer handle is owned by the database. It
+ * must not be written to or freed. To modify the data or use them beyond the
+ * caller's scope, this memory must be copied. Note that the buffer handle
+ * must still be freed, but not the underlying data buffer, since only the
+ * latter is owned by the back end. For stores 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[out] sspo #LSUP_BufferTriple to be populated with three serialized
+ * terms if found. It may be NULL. NOTE: the content of this variable is
+ * undefined on rc != LSUP_OK.
+ *
+ * @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
+ *  is ignored by implementations without the LSUP_STORE_CTX feature.
+ *
+ *  To iterate over the context array, use this loop:
+ *
+ *      size_t i = 0;
+ *      while (ctx[i].addr)
+ *          do_something(ctx + (i++)); // Buffer data are RO.
+ *
+ * @return LSUP_OK if results were found; LSUP_END if no (more) results were
+ * found; LSUP_DB_ERR if a backend error occurred.
+ */
+typedef LSUP_rc (*iter_next_fn_t)(
+        void *it, LSUP_BufferTriple *sspo, LSUP_Buffer **ctx);
+
+
+/** @brief Prototype: free an iterator allocated by a lookup.
+ *
+ * @param it[in] Iterator pointer. It will be set to NULL after freeing.
+ */
+typedef void (*iter_free_fn_t)(void * it);
+
+
+/*
+ * Iterface type definitions.
+ */
+
+/** @brief Store interface.
+ */
+typedef struct store_if_t {
+    // Basic properties.
+    char                name[16];       ///< Store type name.
+    LSUP_StoreFeature   features;       ///< Feature flags.
+
+    // Allocation, setup and deallocation.
+    store_setup_fn_t    setup_fn;       ///< Optional function called before \
+                                            #store_new_fn_t.
+    store_new_fn_t      new_fn;         ///< Create a new store instance.
+    store_free_fn_t     free_fn;        ///< Free the store.
+
+    // Metadata.
+    store_size_fn_t     size_fn;        ///< Number of triples in the store.
+    store_id_fn_t       id_fn;          ///< Get store ID.
+
+    // Addition.
+    store_add_init_fn_t add_init_fn;    ///< Initialize add iteration.
+    store_add_iter_fn_t add_iter_fn;    ///< Add one triple.
+    store_add_abort_fn_t add_abort_fn;  ///< Abort (roll back) the add \
+                                            process.  Only available in \
+                                            stores with #LSUP_STORE_TXN \
+                                            feature. Optional.
+    store_add_done_fn_t add_done_fn;    ///< Complete the add process.
+    store_add_term_fn_t add_term_fn;    ///< Add (index) a term to the store. \
+                                            Only available in stores with \
+                                            #LSUP_STORE_IDX feature. Optional.
+
+    // Look up.
+    store_lookup_fn_t   lookup_fn;      ///< Look up triples by pattern. 
+    //store_trp_exist_fn_t exist_fn;      ///< Check if a triple exists.
+    iter_next_fn_t      lu_next_fn;     ///< Advance the lookup iterator.
+    iter_free_fn_t      lu_free_fn;     ///< Free the lookup iterator.
+
+    // Removal.
+    store_remove_fn_t   remove_fn;      ///< Remove triples by pattern.
+
+    // Namespace prefix mapping.
+    store_nsm_put_fn_t  nsm_put_fn;     ///< Add a namespace/prefix pair to \
+                                            the prefix map. \
+                                            Only available (and mandatory) \
+                                            in stores with the \
+                                            #LSUP_STORE_IDX feature.
+    store_nsm_get_fn_t  nsm_get_fn;     ///< Get a namespace/prefix from \
+                                            the prefix map. \
+                                            Only available (and mandatory) \
+                                            in stores with the \
+                                            #LSUP_STORE_IDX feature.
+} LSUP_StoreInt;
+
+
+/*
+ * Template for a new store and iterator implementation.
+ * These should be placed in the .c file where the interface functions are
+ * defined, and declared as `extern` in the related .h file.
+
+const LSUP_StoreInt my_store_int = {
+    .name           = "My Store", // Truncated to 15 chars.
+    .features       = LSUP_STORE_PERM | LSUP_STORE_IDX,
+
+    .setup_fn       = my_setup_fn,
+    .new_fn         = my_new_fn,
+    .free_fn        = my_free_fn,
+
+    .size_fn        = my_size_fn,
+
+    .add_init_fn    = my_init_fn,
+    .add_iter_fn    = my_iter_fn,
+    .add_abort_fn   = my_add_abort_fn,
+    .add_done_fn    = my_add_done_fn,
+    .add_term_fn    = my_add_term_fn,
+
+    .lookup_fn      = my_lookup_fn,
+    .lu_next_fn     = my_iter_next_fn,
+    .lu_free_fn     = my_iter_free_fn,
+
+    .remove_fn      = my_remove_fn,
+
+    .nsm_put_fn     = my_nsm_put_fn,
+    .nsm_get_fn     = my_nsm_get_fn,
+};
+*/
+
+#endif  /* _LSUP_STORE_BASE_H */

+ 3 - 192
include/store_htable.h

@@ -1,4 +1,4 @@
-/** @file store.htable.h
+/** @file store_htable.h
  *
  * @brief Simple in-memory triple store back end based on hash tables.
  *
@@ -21,198 +21,9 @@
 #define _LSUP_STORE_HTABLE_H
 
 #include "buffer.h"
-#include "store.h"
+#include "store_base.h"
 
 
-typedef struct ht_store_t LSUP_HTStore;
-typedef struct ht_iterator_t LSUP_HTIterator;
-
-LSUP_HTStore *
-LSUP_htstore_new (const size_t size);
-
-
-/** @brief Boolean operation on hash table triples.
- *
- * The resulting store is compacted to the minimum size necessary to hold all
- * results.
- *
- * @param[in] op Operation type. See #LSUP_bool_op
- *
- * @param[in] s1 First store.
- *
- * @param[in] s2 Second store.
- *
- * @return New store resulting from the operation. It must be freed with
- * #LSUP_htstore_free after use.
- */
-LSUP_HTStore *
-LSUP_htstore_bool_op (
-        const LSUP_bool_op op, const LSUP_HTStore *s1, const LSUP_HTStore *s2);
-
-
-/** @brief Free a hash table store.
- */
-void
-LSUP_htstore_free (LSUP_HTStore *ht);
-
-
-/** @brief Duplicate a store.
- *
- * @param[in] src Store to duplicate.
- *
- * @return New store. It must be freed with #LSUP_htstore_free() after use.
- */
-LSUP_HTStore *
-LSUP_htstore_copy (const LSUP_HTStore *src);
-
-
-/** @brief Copy contents of a store to another store.
- *
- * The destination is not initialized, so copy is cumulative with the existing
- * content.
- *
- * @param[in] Store to copy to. It must be already initialized via
- *  #LSUP_htstore_new(), #LSUP_htstore_copy(), etc.
- *
- * @param[in] src Store to copy from.
- */
-LSUP_rc
-LSUP_htstore_copy_contents (LSUP_HTStore *dest, const LSUP_HTStore *src);
-
-/** @brief Count triples in a store.
- *
- * @parm[in] store HTStore handle.
- *
- * @return Number of triples in the store.
- */
-size_t
-LSUP_htstore_size (const LSUP_HTStore *ht);
-
-
-/** @brief Add a term to the store.
- *
- * @parm[in] store HTStore handle.
- *
- * @param[in] sterm Serialized term to insert. The term is copied and may be
- *  safely freed after the operation.
- *
- * @return LSUP_OK on success; LSUP_NOACTION if the term exists already; <0
- *  on error.
- */
-LSUP_rc
-LSUP_htstore_add_term (LSUP_HTStore *store, const LSUP_Buffer *sterm);
-
-
-/** @brief Initialize a loop to add triples to a store.
- *
- * @param[in] store Store handler.
- *
- * @return Iterator to be used with #LSUP_htstore_add_iter(). It must be freed
- *  with #LSUP_htstore_add_done().
- */
-LSUP_HTIterator *
-LSUP_htstore_add_init (LSUP_HTStore *store);
-
-
-/** @brief Add triples to the store.
- *
- * @param[in] it Iterator handle created with #LSUP_htstore_add_init().
- *
- * @param[in] sspo Serialized buffer triple to add.
- */
-LSUP_rc
-LSUP_htstore_add_iter (LSUP_HTIterator *it, const LSUP_BufferTriple *sspo);
-
-
-/** @brief Free resources related to an add loop.
- *
- * @param[in] it Iterator to free.
- */
-void
-LSUP_htstore_add_done (LSUP_HTIterator *it);
-
-/** @brief Find triples by pattern matching and return an iterator.
- *
- * The iterator may yield results by using #LSUP_htiter_next() and must be
- * freed with #LSUP_htiter_free().
- *
- * @param[in] store Store to search in.
- *
- * @param[in] ss Serialized subject term. If NULL, the term is unbound.
- *
- * @param[in] sp Serialized predicate term. If NULL, the term is unbound.
- *
- * @param[in] so Serialized object term. If NULL, the term is unbound.
- *
- * @param[out] ct If not NULL, it is populated with the number of results. Use
- *  only if necessary: since the hash table is not indexed, retrieving the
- *  count requires looping over the results, thus slowing down the operation.
- *
- * @return Iterator for lookup results.
- */
-LSUP_HTIterator *
-LSUP_htstore_lookup (
-        LSUP_HTStore *store, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
-        const LSUP_Buffer *so, size_t *ct);
-
-
-/** @brief Find the next triple in a lookup and return the result.
- *
- * @param[in] it Iterator obtained from #LSUP_htstore_lookup().
- *
- * @param[out] spo Serialized triple pointer to be populated with the result,
- *  if found.
- *
- * @return LSUP_OK if a result was found; LSUP_END if the end of the iterator
- *  is reached.
- */
-LSUP_rc
-LSUP_htiter_next (LSUP_HTIterator *it, LSUP_BufferTriple *sspo);
-
-
-/** @brief Count of lookup results or triples added in an iteration.
- *
- * @param[in] it Iterator handle.
- *
- * @return Number of results yielded, or triples added, at a certain point of
- *  an iterator.
- */
-/*
-size_t
-LSUP_htiter_count (const LSUP_HTIterator *it);
-*/
-
-
-/** @brief Free an iterator.
- *
- * @param[in] it Iterator handle obtained from #LSUP_htstore_lookup().
- */
-void
-LSUP_htiter_free (LSUP_HTIterator *it);
-
-
-/** @brief Remove triples by pattern matching.
- *
- * The search criteria are the same used for #LSUP_htstore_lookup().
- *
- * @param[in] store Store to remove triples from.
- *
- * @param[in] ss Serialized subject term. If NULL, the term is unbound.
- *
- * @param[in] sp Serialized predicate term. If NULL, the term is unbound.
- *
- * @param[in] so Serialized object term. If NULL, the term is unbound.
- *
- * @param[out] Optional pointer to a counter. If not NULL, it is populated with
- *      the number of triples removed. It is undefined if LSUP_DB_ERR is
- *      returned.
- *
- * @return LSUP_OK if any triples were deleted; LSUP_NOACTION if no triples
- *      were found for deletion; <0 on error.
- */
-LSUP_rc
-LSUP_htstore_remove (
-        LSUP_HTStore *store, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
-        const LSUP_Buffer *so, size_t *ct);
+extern const LSUP_StoreInt htstore_int;
 
 #endif  // _LSUP_STORE_HTABLE_H

+ 4 - 8
include/store_mdb.h

@@ -25,17 +25,13 @@
 #include "lmdb.h"
 
 #include "buffer.h"
-#include "store.h"
+#include "store_base.h"
 
 
 // FIXME find a better cross-platform path.
-#define DEFAULT_ENV_PATH "./mdb_store"
+#define LSUP_MDB_STORE_URN "file://" TMPDIR "/mdb_store"
 
-// RAMdisk path for MDB volatile store.
-#define LSUP_MDB_RAMDISK_PATH TMPDIR "/lsup_mem_graph"
-
-
-extern const LSUP_StoreInt mdbstore_int;       /// MDB store interface.
-extern const LSUP_StoreIteratorInt mdbiter_int;  /// MDB iterator interface.
+/// MDB store interface.
+extern const LSUP_StoreInt mdbstore_int;
 
 #endif  /* _LSUP_STORE_MDB_H */

+ 2 - 2
src/codec_nt.c

@@ -3,13 +3,13 @@
 
 /** @brief List of characters to be escaped in serialized literals.
  *
- * https://www.w3.org/TR/n-triples/#grammar-production-ECHAR
+ * @sa https://www.w3.org/TR/n-triples/#grammar-production-ECHAR
  */
 #define LIT_ECHAR "\t\b\n\r\f\"\'\\"
 
 /** @brief Regex of characters to be escaped in serialized IRIs.
  *
- * https://www.w3.org/TR/n-triples/#grammar-production-IRIREF
+ * @sa https://www.w3.org/TR/n-triples/#grammar-production-IRIREF
  */
 #define IRI_ECHAR_PTN "[\x00-\x20<>\"\\{\\}\\|\\^`\\\\]"
 

+ 1 - 1
src/core.c

@@ -36,7 +36,7 @@ char *err_msg[] = {
     "LSUP_IO_ERR: Input/Output error.",
     "LSUP_MEM_ERR: Memory error.",
     "LSUP_CONFLICT_ERR: A resource conflict interrupted the operation.",
-    "LSUP_ENV_ERR: Invalid environment. Did you call LSUP_init()?",
+    "LSUP_ENV_ERR: Environment not initialized. Did you call LSUP_init()?",
 };
 
 char *LSUP_root_path = __FILE__; // This is trimmed to root path on init.

+ 68 - 42
src/environment.c

@@ -1,28 +1,43 @@
+#if 0
 #include <unistd.h>
+#endif
 
-#include "term.h"
 #include "environment.h"
+#include "data/bootstrap.h"
 
 
-/**
- * Static handles.
+/** @brief Default context.
+ *
+ * The actual default context IRI used throughout the application may use a
+ * value from the `LSUP_DEFAULT_CTX_URI` environment variable.
  */
 #define DEFAULT_CTX_LABEL LSUP_NS "default"
 
 
+#if 0
 LSUP_Env *LSUP_default_env = NULL; /// Environment "singleton".
+#endif
+
+/*
+ * External variables.
+ */
+LSUP_NSMap *LSUP_default_nsm = NULL;
+LSUP_Term *LSUP_default_ctx = NULL;
+LSUP_Buffer *LSUP_default_ctx_buf = NULL;
 
 
 /*
  * Hashmap callbacks.
  */
 
-static uint64_t term_cache_hash_fn (
+static uint64_t
+term_cache_hash_fn (
         const void *item, uint64_t seed0, uint64_t seed1)
 { return ((const LSUP_KeyedTerm *) item)->key; }
 
 
-static int term_cache_cmp_fn (const void *a, const void *b, void *udata)
+static int
+term_cache_cmp_fn (const void *a, const void *b, void *udata)
 {
     return 
             ((const LSUP_KeyedTerm *) a)->key -
@@ -30,7 +45,8 @@ static int term_cache_cmp_fn (const void *a, const void *b, void *udata)
 }
 
 
-static void term_cache_free_fn (void *item)
+static void
+term_cache_free_fn (void *item)
 { LSUP_term_free (((LSUP_KeyedTerm *) item)->term); }
 
 
@@ -38,10 +54,9 @@ static void term_cache_free_fn (void *item)
  * API
  */
 
+#if 0
 LSUP_Env *
-LSUP_env_new (
-        const char *default_ctx, const char *mdb_path,
-        const LSUP_NSMap *nsmap)
+LSUP_env_new (const char *default_ctx, const LSUP_NSMap *nsmap)
 {
     LSUP_Env *env;
     CALLOC_GUARD (env, NULL);
@@ -52,25 +67,18 @@ LSUP_env_new (
     LSUP_term_free (default_ctx_uri);
     log_info ("Set up default context.");
 
-    // Set up store if not existing.
-    if (LSUP_mdbstore_setup (mdb_path, false) != LSUP_OK) return NULL;
-    env->mdb_store = LSUP_mdbstore_new (mdb_path, env->default_ctx);
-    if (UNLIKELY (!env->mdb_store)) return NULL;
-    log_info ("Initialized persistent back end at %s.", mdb_path);
-
-    // Get default namespace from store.
-    RCNL (LSUP_mdbstore_nsm_get (env->mdb_store, &env->nsm));
-
     return env;
 }
+#endif
 
 
 LSUP_rc
 LSUP_init (void)
 {
-    LSUP_rc rc = LSUP_NOACTION;
-
-    if (LSUP_env_is_init) return rc;
+    if (LSUP_is_init()) {
+        LOG_RC (LSUP_NOACTION);
+        return LSUP_NOACTION;
+    }
 
 #ifdef DEBUG
     // In debug mode, always use max logging.
@@ -83,7 +91,8 @@ LSUP_init (void)
 
     // URI validation pattern.
     MALLOC_GUARD (LSUP_uri_ptn, LSUP_MEM_ERR);
-    /* Uncomment in case a change in the URI regex results in an error.
+
+#if 0 // Re-activate in case a change in the URI regex results in an error.
     int regex_rc = regcomp (LSUP_uri_ptn, LSUP_URI_REGEX_STR, REG_EXTENDED);
     if (regex_rc != 0) {
         char err_msg[128];
@@ -93,11 +102,26 @@ LSUP_init (void)
                 err_msg);
         return LSUP_ERROR;
     }
-    */
+#else
     if (regcomp (LSUP_uri_ptn, LSUP_URI_REGEX_STR, REG_EXTENDED) != 0) {
         log_error ("Error compiling regular expression pattern.");
         return LSUP_ERROR;
     }
+#endif
+
+    // Default namespace map.
+    LSUP_default_nsm = LSUP_nsmap_new();
+    if (UNLIKELY (!LSUP_default_nsm)) return LSUP_ERROR;
+    for (int i = 0; init_nsmap[i][0] != NULL; i++)
+        LSUP_nsmap_add (LSUP_default_nsm, init_nsmap[i][0], init_nsmap[i][1]);
+
+    // Default context URI.
+    char *default_ctx_str = getenv ("LSUP_DEFAULT_CTX");
+    if (!default_ctx_str ) default_ctx_str = DEFAULT_CTX_LABEL;
+    LSUP_default_ctx = LSUP_iriref_new (default_ctx_str, LSUP_default_nsm);
+    if (UNLIKELY (!LSUP_default_ctx)) return LSUP_ERROR;
+    LSUP_default_ctx_buf = LSUP_term_serialize (LSUP_default_ctx);
+    if (UNLIKELY (!LSUP_default_ctx_buf)) return LSUP_ERROR;
 
     // Initialize term cache.
     LSUP_term_cache = hashmap_new (
@@ -108,20 +132,9 @@ LSUP_init (void)
     uint32_t dtype_hash = LSUP_term_hash (LSUP_default_datatype );
     LSUP_tcache_add (dtype_hash, LSUP_default_datatype);
 
-    // Default permanent store path.
-    char *mdb_path = getenv ("LSUP_MDB_STORE_PATH");
-    if (!mdb_path) {
-        mdb_path = DEFAULT_ENV_PATH;
-        log_warn (
-            "`LSUP_MDB_STORE_PATH' environment variable is not "
-            "set. The default location %s will be used as the graph "
-            "store.", mdb_path
-        );
-    }
-
+#if 0
     // Default permanent store.
-    LSUP_default_env = LSUP_env_new (
-            DEFAULT_CTX_LABEL, mdb_path, NULL);
+    LSUP_default_env = LSUP_env_new (DEFAULT_CTX_LABEL, NULL);
     if (UNLIKELY (!LSUP_default_env)) return LSUP_ERROR;
 
     // RAM disk store.
@@ -138,16 +151,15 @@ LSUP_init (void)
     }
 
     log_info ("Initialized RAM disk back end at %s.", LSUP_MDB_RAMDISK_PATH);
+#endif
 
     // Set automatic teardown TODO Is this a good idea?
     atexit (LSUP_done);
 
-    LSUP_env_is_init = true;
-
     return LSUP_OK;
 }
 
-
+#if 0
 void
 LSUP_env_free (LSUP_Env *env)
 {
@@ -158,19 +170,33 @@ LSUP_env_free (LSUP_Env *env)
 
     free (env);
 }
+#endif
 
 
 void
 LSUP_done (void)
 {
-    if (!LSUP_env_is_init) return;
+    if (!LSUP_is_init()) return;
 
+#if 0
     LSUP_env_free (LSUP_default_env);
+#endif
     regfree (LSUP_uri_ptn);
     free (LSUP_uri_ptn);
 
+    // Free default NS map and context.
+    LSUP_buffer_free (LSUP_default_ctx_buf);
+    LSUP_term_free (LSUP_default_ctx);
+    LSUP_nsmap_free (LSUP_default_nsm);
+
     // Free ID cache, including default literal datatype.
     hashmap_free (LSUP_term_cache);
-
-    LSUP_env_is_init = false;
+    LSUP_term_cache = NULL; // This causes LSUP_is_init() to return false.
 }
+
+
+/*
+ * Extern inline prototypes.
+ */
+
+bool LSUP_is_init (void);

+ 217 - 216
src/graph.c

@@ -1,29 +1,21 @@
-#include "store_htable.h"
-#include "store_mdb.h"
 #include "graph.h"
 
 /*
  * Data types.
  */
 
-typedef struct Graph {
-    const LSUP_Env *        env;            ///> LSUP environment.
-    LSUP_Term               *uri;           ///> Graph "name" (URI)
-    const LSUP_StoreInt *   interface;      ///> Store interface.
-    void *                  backend;        ///> Store back end struct.
-    LSUP_NSMap *            nsm;            ///> Namespace map. NOTE: This is
-                                            ///> NULL for MDB* stores. To get
-                                            ///> a proper NSMap, always call
-                                            ///> #LSUP_graph_namespace() for
-                                            ///> all types of graphs.
-} Graph;
-
-typedef struct GraphIterator {
-    const Graph *                   graph;      ///> Parent graph.
-    const LSUP_StoreIteratorInt *   interface;  ///> Iterator interface.
-    void *                          backend;    ///> Iterator back end struct.
-    size_t                          ct;         ///> Total matches.
-} GraphIterator;
+struct graph_t {
+    LSUP_Term               *uri;           ///< Graph "name" (URI).
+    LSUP_Store *            store;          ///< Store handle.
+    LSUP_NSMap *            nsm;            /**< Namespace map. NOTE: This is
+                                               * NULL for permanent stores. */
+};
+
+struct graph_iter_t {
+    LSUP_Store *            store;          ///< Store tied to the iterator.
+    void *                  data;           ///< Iterator state.
+    size_t                  ct;             ///< Total lookup matches.
+};
 
 
 /*
@@ -31,14 +23,11 @@ typedef struct GraphIterator {
  */
 
 inline static LSUP_rc
-graph_iter_next_buffer (GraphIterator *it, LSUP_BufferTriple *sspo);
-
-static LSUP_rc
-graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest);
+graph_iter_next_buffer (LSUP_GraphIterator *it, LSUP_BufferTriple *sspo);
 
-#define ENTRY(a, b, c) (be) == (LSUP_STORE_##a) ||
+#define ENTRY(a, b) (be) == (LSUP_STORE_##a) ||
 static inline bool
-check_backend (LSUP_store_type be)
+check_backend (LSUP_StoreType be)
 { return (BACKEND_TBL false); }
 #undef ENTRY
 
@@ -47,12 +36,14 @@ check_backend (LSUP_store_type be)
  * Graph API.
  */
 
-Graph *
-LSUP_graph_new_env (
-        const LSUP_Env *env, LSUP_Term *uri, const LSUP_store_type store_type)
+LSUP_Graph *
+LSUP_graph_new (
+        LSUP_Term *uri, const LSUP_StoreType store_type, const char *store_id,
+        LSUP_NSMap *nsm, size_t size)
 {
-    if (UNLIKELY (!env)) {
-        log_error ("No valid environment passed. Did you call LSUP_init()?");
+    if (UNLIKELY (!LSUP_is_init)) {
+        log_error (
+                "Environment is not initialized. Did you call LSUP_init()?");
         return NULL;
     }
     if (UNLIKELY (!check_backend (store_type))) return NULL;
@@ -61,32 +52,40 @@ LSUP_graph_new_env (
     MALLOC_GUARD (gr, NULL);
 
     gr->uri = uri;
-    gr->env = env;
-    gr->nsm = env->nsm;
+    MALLOC_GUARD (gr->store, NULL);
 
-    switch (store_type) {
-#define ENTRY(a, b, c) case LSUP_STORE_##a: gr->interface = &b; break;
+    gr->store->type = store_type;
+    gr->store->id = strdup (store_id);
+    if (UNLIKELY (!gr->store->id)) return NULL;
+
+    switch (gr->store->type) {
+#define ENTRY(a, b) \
+        case LSUP_STORE_##a: gr->store->sif = &b; break;
 BACKEND_TBL
 #undef ENTRY
         default:
             log_error ("Not a valid store type: %d", store_type); return NULL;
     };
 
-    gr->backend = gr->interface->new_fn();
+    // TODO implement custom default context.
+    gr->store->data = gr->store->sif->new_fn (store_id, size);
+
+    if (gr->store->sif->features & LSUP_STORE_PERM) gr->nsm = NULL;
+    else gr->nsm = nsm ? nsm : LSUP_default_nsm;
 
     log_debug ("Graph created.");
     return gr;
 }
 
 
+#if 0
 LSUP_Graph *
-LSUP_graph_copy (const Graph *src)
+LSUP_graph_copy (const LSUP_Graph *src, LSUP_Term *uri)
 {
-    LSUP_Graph *dest = LSUP_graph_new_env (
-            src->env, LSUP_iriref_new (NULL, NULL), src->store_type);
+    LSUP_Graph *dest = LSUP_graph_new (uri, src->store_type, src->nsm);
     if (UNLIKELY (!dest)) return NULL;
 
-    LSUP_rc rc = graph_copy_contents (src, dest);
+    LSUP_rc rc = LSUP_graph_copy_contents (src, dest);
     if (UNLIKELY (rc != LSUP_OK)) return NULL;
 
     return dest;
@@ -107,7 +106,7 @@ LSUP_graph_store (
     if (UNLIKELY (!dest)) return LSUP_DB_ERR;
 
     LSUP_rc rc;
-    rc = graph_copy_contents (src, dest);
+    rc = LSUP_graph_copy_contents (src, dest);
     if (UNLIKELY (rc < 0)) return LSUP_DB_ERR;
 
     if (src->store_type == LSUP_STORE_MEM) {
@@ -120,49 +119,95 @@ LSUP_graph_store (
 
     return LSUP_OK;
 }
+#endif
 
 
-// TODO support boolean ops between any types of graphs.
-Graph *
+LSUP_rc
 LSUP_graph_bool_op(
-        const LSUP_bool_op op, const Graph *gr1, const Graph *gr2)
+        const LSUP_bool_op op, const LSUP_Graph *gr1, const LSUP_Graph *gr2,
+        LSUP_Graph *res)
 {
-    if (UNLIKELY (gr1->store_type != LSUP_STORE_MEM)) {
-        fprintf(
-                stderr,
-                "First operand %s is not an in-memory graph. "
-                "Cannot perform boolean operation.",
-                gr1->uri->data);
-        return NULL;
+    LSUP_rc rc;
+    if (UNLIKELY (
+            op != LSUP_BOOL_UNION
+            && op != LSUP_BOOL_SUBTRACTION
+            && op != LSUP_BOOL_INTERSECTION
+            && op != LSUP_BOOL_XOR)) {
+        log_error ("Invalid boolean operation: %d.", op);
+
+        rc = LSUP_VALUE_ERR;
+        goto fail;
     }
-    if (UNLIKELY (gr2->store_type != LSUP_STORE_MEM)) {
-        fprintf(
-                stderr,
-                "Second operand %s is not an in-memory graph. "
-                "Cannot perform boolean operation.",
-                gr2->uri->data);
-        return NULL;
+
+    if (op == LSUP_BOOL_UNION) {
+        PCHECK (LSUP_graph_copy_contents (gr1, res), fail);
+        PCHECK (LSUP_graph_copy_contents (gr2, res), fail);
+
+        return LSUP_OK;
     }
 
-    LSUP_Graph *res = LSUP_graph_new (
-            LSUP_iriref_new (NULL, NULL), LSUP_STORE_MEM);
-    res->ht_store = LSUP_htstore_bool_op (op, gr1->ht_store, gr2->ht_store);
+    LSUP_Buffer
+        *res_sc = LSUP_term_serialize (res->uri),
+        *gr1_sc = LSUP_term_serialize (gr1->uri),
+        *gr2_sc = LSUP_term_serialize (gr2->uri);
+    void *lu1_it, *lu2_it, *add_it;
+    LSUP_BufferTriple *sspo;
+    size_t ct;
+
+    add_it = res->store->sif->add_init_fn (res->store->data, res_sc);
+
+    size_t i = 0;
+    if (op == LSUP_BOOL_XOR) {
+        // Add triples from gr2 if not found in gr1.
+        lu2_it = gr2->store->sif->lookup_fn (
+                gr2->store->data, NULL, NULL, NULL, gr2_sc, NULL);
+        while (gr2->store->sif->lu_next_fn (lu2_it, sspo, NULL) == LSUP_OK) {
+            lu1_it = gr1->store->sif->lookup_fn (
+                    gr1->store->data, sspo->s, sspo->p, sspo->o, gr1_sc, &ct);
+            if (ct > 0)
+                res->store->sif->add_iter_fn (add_it, sspo);
+            gr1->store->sif->lu_free_fn (lu1_it);
+        }
+        gr2->store->sif->lu_free_fn (lu2_it);
+    }
 
-    return res;
+    lu1_it = gr1->store->sif->lookup_fn (
+            gr1->store->data, NULL, NULL, NULL, gr1_sc, NULL);
+    while (gr1->store->sif->lu_next_fn (lu1_it, sspo, NULL) == LSUP_OK) {
+        lu2_it = gr2->store->sif->lookup_fn (
+                gr2->store->data, sspo->s, sspo->p, sspo->o, gr2_sc, &ct);
+        // For XOR and subtraction, add if not found.
+        // For intersection, add if found.
+        if (ct == 0 ^ op == LSUP_BOOL_INTERSECTION)
+            res->store->sif->add_iter_fn (add_it, sspo);
+        gr2->store->sif->lu_free_fn (lu2_it);
+    }
+    gr1->store->sif->lu_free_fn (lu1_it);
+
+    res->store->sif->add_done_fn (add_it);
+    LSUP_buffer_free (res_sc);
+    LSUP_buffer_free (gr1_sc);
+    LSUP_buffer_free (gr2_sc);
+
+    return rc;
+
+fail:
+    LSUP_graph_free (res);
+    return rc;
 }
 
 
 void
 LSUP_graph_free (LSUP_Graph *gr)
 {
-    if (LIKELY (gr != NULL)) {
-        LSUP_term_free (gr->uri);
+    if (UNLIKELY (!gr)) return;
 
-        if (gr->store_type == LSUP_STORE_MEM)
-            LSUP_htstore_free (gr->ht_store);
+    LSUP_term_free (gr->uri);
+    free (gr->store->id);
+    gr->store->sif->free_fn (gr->store->data);
+    free (gr->store);
 
-        free (gr);
-    }
+    free (gr);
 }
 
 
@@ -186,61 +231,53 @@ LSUP_graph_set_uri (LSUP_Graph *gr, LSUP_Term *uri)
 
 
 LSUP_NSMap *
-LSUP_graph_namespace (const Graph *gr)
+LSUP_graph_namespace (const LSUP_Graph *gr)
 {
-    if (gr->store_type == LSUP_STORE_MEM) return gr->nsm;
+    // If nsm_get_fn is not defined, the store has no own NS map.
+    if (!gr->store->sif->nsm_get_fn) return gr->nsm;
 
-    LSUP_NSMap *nsm;
-    if (LSUP_mdbstore_nsm_get (gr->mdb_store, &nsm) < 0) return NULL;
-    return nsm;
+    return gr->store->sif->nsm_get_fn (gr->store->data);
 }
 
 
 void
-LSUP_graph_set_namespace (Graph *gr, LSUP_NSMap *nsm)
-{ if (gr->store_type == LSUP_STORE_MEM) gr->nsm = nsm; }
-
-
-size_t
-LSUP_graph_size (const Graph *gr)
+LSUP_graph_set_namespace (LSUP_Graph *gr, LSUP_NSMap *nsm)
 {
-    if (gr->store_type == LSUP_STORE_MEM)
-        return LSUP_htstore_size (gr->ht_store);
-    else {
-        size_t ct;
-        LSUP_GraphIterator *it = LSUP_graph_lookup (gr, NULL, NULL, NULL, &ct);
+    if (!gr->store->sif->nsm_get_fn) gr->nsm = nsm;
+    else log_warn ("Graph back end has a stored NS map.");
+}
 
-        LSUP_graph_iter_free (it);
 
-        return ct;
-    }
-}
+size_t
+LSUP_graph_size (const LSUP_Graph *gr)
+{ return gr->store->sif->size_fn (gr->store->data); }
 
 
 bool
-LSUP_graph_equals (const Graph *gr1, const Graph *gr2)
+LSUP_graph_equals (const LSUP_Graph *gr1, const LSUP_Graph *gr2)
 {
-    LSUP_Graph *res = LSUP_graph_bool_op (LSUP_BOOL_XOR, gr1, gr2);
+    LSUP_Graph *res = LSUP_graph_new (NULL, LSUP_STORE_HTABLE, NULL, NULL, 0);
+    LSUP_graph_bool_op (LSUP_BOOL_XOR, gr1, gr2, res);
+    bool ret = (LSUP_graph_size (res) == 0);
 
-    return (LSUP_graph_size (res) == 0);
+    LSUP_graph_free (res);
+
+    return ret;
 }
 
 
-GraphIterator *
+LSUP_GraphIterator *
 LSUP_graph_add_init (LSUP_Graph *gr)
 {
-    GraphIterator *it;
+    LSUP_GraphIterator *it;
     CALLOC_GUARD (it, NULL);
 
-    if (gr->store_type == LSUP_STORE_MEM) {
-        it->ht_iter = LSUP_htstore_add_init (gr->ht_store);
-    } else {
-        LSUP_Buffer *sc = LSUP_term_serialize (gr->uri);
-        it->mdb_iter = LSUP_mdbstore_add_init (gr->mdb_store, sc);
-        LSUP_buffer_free (sc);
-    }
+    LSUP_Buffer *sc = LSUP_term_serialize (gr->uri);
+
+    it->data = gr->store->sif->add_init_fn (gr->store->data, sc);
+    LSUP_buffer_free (sc);
 
-    it->graph = gr;
+    it->store = gr->store;
 
     return it;
 }
@@ -254,35 +291,22 @@ LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo)
     LSUP_BufferTriple *sspo = LSUP_triple_serialize (spo);
     if (UNLIKELY (!sspo)) return LSUP_MEM_ERR;
 
-    if (it->graph->store_type == LSUP_STORE_MEM) {
-        rc = LSUP_htstore_add_iter (it->ht_iter, sspo);
-
-        for (int i = 0; i < 3; i++) {
-            LSUP_htstore_add_term (
-                    it->graph->ht_store, LSUP_btriple_pos (sspo, i));
-            // HT store uses term keys from tcache.
-        }
-    } else {
-        rc = LSUP_mdbstore_add_iter (it->mdb_iter, sspo);
+    PCHECK (it->store->sif->add_iter_fn (it->data, sspo), finally);
 
+    // Store datatype term permanently if the store supports it.
+    if (it->store->sif->add_term_fn) {
         for (int i = 0; i < 3; i++) {
-            LSUP_mdbstore_add_term (
-                    it->graph->mdb_store, LSUP_btriple_pos (sspo, i));
-
-            // Store datatype term permanently.
             LSUP_Term *term = LSUP_triple_pos (spo, i);
-            if (
-                term->type == LSUP_TERM_LITERAL
-                && !LSUP_mdbstore_tkey_exists (
-                        it->graph->mdb_store, LSUP_term_hash (term->datatype))
-            ) {
+            if (term->type == LSUP_TERM_LITERAL) {
                 LSUP_Buffer *ser_dtype = LSUP_term_serialize (term->datatype);
-                LSUP_mdbstore_add_term (it->graph->mdb_store, ser_dtype);
+                it->store->sif->add_term_fn (it->store->data, ser_dtype);
                 LSUP_buffer_free (ser_dtype);
             }
         }
     }
 
+
+finally:
     LSUP_btriple_free (sspo);
 
     return rc;
@@ -291,18 +315,11 @@ LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo)
 
 void
 LSUP_graph_add_done (LSUP_GraphIterator *it)
-{
-    if (it->graph->store_type == LSUP_STORE_MEM)
-        LSUP_htstore_add_done (it->ht_iter);
-    else LSUP_mdbstore_add_done (it->mdb_iter);
-
-    free (it);
-    log_trace ("Done adding.");
-}
+{ it->store->sif->add_done_fn (it->data); }
 
 
 LSUP_rc
-LSUP_graph_add (Graph *gr, const LSUP_Triple trp[], size_t *ct)
+LSUP_graph_add (LSUP_Graph *gr, const LSUP_Triple trp[], size_t *ct)
 {
     LSUP_rc rc = LSUP_NOACTION;
 
@@ -322,9 +339,13 @@ LSUP_graph_add (Graph *gr, const LSUP_Triple trp[], size_t *ct)
             // A duplicate will return LSUP_NOACTION and not increment the
             // counter.
         }
-        if (UNLIKELY (db_rc < 0)) return db_rc;
+        if (UNLIKELY (db_rc < 0)) {
+            rc = db_rc;
+            goto finally;
+        }
     }
 
+finally:
     LSUP_graph_add_done (it);
 
     return rc;
@@ -344,10 +365,7 @@ LSUP_graph_remove (
         *so = LSUP_term_serialize (o),
         *sc = LSUP_term_serialize (gr->uri);
 
-    if (gr->store_type == LSUP_STORE_MEM)
-        rc = LSUP_htstore_remove (gr->ht_store, ss, sp, so, ct);
-    else
-        rc = LSUP_mdbstore_remove (gr->mdb_store, ss, sp, so, sc, ct);
+    rc = gr->store->sif->remove_fn (gr->store->data, ss, sp, so, sc, ct);
 
     LSUP_buffer_free (ss);
     LSUP_buffer_free (sp);
@@ -358,26 +376,59 @@ LSUP_graph_remove (
 }
 
 
-GraphIterator *
-LSUP_graph_lookup (const Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
+/**
+ * Copy triples from a source graph into a destination one.
+ *
+ * The destination graph is not initialized here, so the copy is cumulative.
+ */
+LSUP_rc
+LSUP_graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest)
+{
+    LSUP_rc rc = LSUP_NOACTION;
+
+    LSUP_GraphIterator *it = LSUP_graph_lookup (src, NULL, NULL, NULL, NULL);
+
+    LSUP_Triple spo;
+
+    LSUP_GraphIterator *add_it = LSUP_graph_add_init (dest);
+    while (LSUP_graph_iter_next (it, &spo) != LSUP_END) {
+        LSUP_rc add_rc = LSUP_graph_add_iter (add_it, &spo);
+        LSUP_triple_done (&spo);
+        if (LIKELY (add_rc == LSUP_OK)) rc = LSUP_OK;
+        else if (add_rc < 0) {
+            rc = add_rc;
+            break;
+        }
+    }
+
+    LSUP_graph_add_done (add_it);
+    LSUP_graph_iter_free (it);
+
+    return rc;
+}
+
+
+LSUP_GraphIterator *
+LSUP_graph_lookup (
+        const LSUP_Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
         const LSUP_Term *o, size_t *ct)
 {
-    GraphIterator *it;
+    LSUP_GraphIterator *it;
     MALLOC_GUARD (it, NULL);
 
-    it->graph = gr;
+    it->store = gr->store;
 
-    LSUP_Buffer *ss = LSUP_term_serialize (s);
-    LSUP_Buffer *sp = LSUP_term_serialize (p);
-    LSUP_Buffer *so = LSUP_term_serialize (o);
-    LSUP_Buffer *sc = LSUP_term_serialize (gr->uri);
-
-    if (it->graph->store_type == LSUP_STORE_MEM)
-        it->ht_iter = LSUP_htstore_lookup (
-                it->graph->ht_store, ss, sp, so, ct);
+    LSUP_Buffer
+        *ss = LSUP_term_serialize (s),
+        *sp = LSUP_term_serialize (p),
+        *so = LSUP_term_serialize (o),
+        *sc = LSUP_term_serialize (gr->uri);
 
-    else it->mdb_iter = LSUP_mdbstore_lookup (
-            it->graph->mdb_store, ss, sp, so, sc, ct);
+    it->data = it->store->sif->lookup_fn (it->store, ss, sp, so, sc, ct);
+    if (UNLIKELY (!it->data)) {
+        free (it);
+        it = NULL;
+    }
 
     LSUP_buffer_free (ss);
     LSUP_buffer_free (sp);
@@ -389,27 +440,18 @@ LSUP_graph_lookup (const Graph *gr, const LSUP_Term *s, const LSUP_Term *p,
 
 
 LSUP_rc
-LSUP_graph_iter_next (GraphIterator *it, LSUP_Triple *spo)
+LSUP_graph_iter_next (LSUP_GraphIterator *it, LSUP_Triple *spo)
 {
-    /*
-     * NOTE: Memory and MDB back ends treat sspo differently, whereas the
-     * memory one owns the whole buffer structure, while the MDB one owns only
-     * the data. Therefore they must be initialized and freed differently.
-     */
-
-    LSUP_BufferTriple *sspo;
     LSUP_Buffer *ss, *sp, *so;
-
-    if (it->graph->store_type == LSUP_STORE_MEM) {
-        ss = sp = so = NULL;
-    } else {
-        // Craft buffers manually so that their addresses are NULL and need not
-        // be freed.
+    LSUP_BufferTriple *sspo;
+    if (it->store->sif->features & LSUP_STORE_COW) {
         CALLOC_GUARD (ss, LSUP_MEM_ERR);
         CALLOC_GUARD (sp, LSUP_MEM_ERR);
         CALLOC_GUARD (so, LSUP_MEM_ERR);
+        sspo = LSUP_btriple_new (ss, sp, so);
+    } else {
+        // TODO copy-on-retrieval stores. None yet.
     }
-    sspo = LSUP_btriple_new (ss, sp, so);
 
     LSUP_rc rc = graph_iter_next_buffer (it, sspo);
 
@@ -422,21 +464,20 @@ LSUP_graph_iter_next (GraphIterator *it, LSUP_Triple *spo)
         if (!spo->o) return LSUP_ERROR;
     }
 
-    if (it->graph->store_type == LSUP_STORE_MEM) free (sspo);
-    else LSUP_btriple_free_shallow (sspo);
+    if (it->store->sif->features & LSUP_STORE_COW) {
+        LSUP_btriple_free_shallow (sspo);
+    } else {
+        // TODO copy-on-retrieval stores. None yet.
+    }
 
     return rc;
 }
 
 
 void
-LSUP_graph_iter_free (GraphIterator *it)
+LSUP_graph_iter_free (LSUP_GraphIterator *it)
 {
-    if (it->graph->store_type == LSUP_STORE_MEM)
-        LSUP_htiter_free (it->ht_iter);
-    else
-        LSUP_mdbiter_free (it->mdb_iter);
-
+    it->store->sif->lu_free_fn (it->data);
     free (it);
 }
 
@@ -444,7 +485,7 @@ LSUP_graph_iter_free (GraphIterator *it)
 bool
 LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
 {
-    GraphIterator *it = LSUP_graph_lookup (
+    LSUP_GraphIterator *it = LSUP_graph_lookup (
             gr, spo->s, spo->p, spo->o, NULL);
     LSUP_Triple *tmp_spo = TRP_DUMMY;
     bool rc = LSUP_graph_iter_next (it, tmp_spo) != LSUP_END;
@@ -460,54 +501,14 @@ LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
  * Static functions.
  */
 
-/** @brief Advance iterator and return serialized triple.
+/** @brief Advance an iterator and return a serialized triple.
  *
  * This is an internal function to pass raw buffers between higher-level
  * functions without serializing and deserializing triples.
  */
 inline static LSUP_rc
-graph_iter_next_buffer (GraphIterator *it, LSUP_BufferTriple *sspo)
-{
-    LSUP_rc rc;
-
-    if (it->graph->store_type == LSUP_STORE_MEM)
-        rc = LSUP_htiter_next (it->ht_iter, sspo);
-    else rc = LSUP_mdbiter_next (it->mdb_iter, sspo, NULL);
-
-    return rc;
-}
-
-
-/**
- * Copy triples from a source graph into a destination one.
- *
- * The destination graph is not initialized here, so the copy is cumulative.
- */
-static LSUP_rc
-graph_copy_contents (const LSUP_Graph *src, LSUP_Graph *dest)
-{
-    LSUP_rc rc = LSUP_NOACTION;
-
-    GraphIterator *it = LSUP_graph_lookup (src, NULL, NULL, NULL, NULL);
-
-    LSUP_Triple spo;
-
-    LSUP_GraphIterator *add_it = LSUP_graph_add_init (dest);
-    while (LSUP_graph_iter_next (it, &spo) != LSUP_END) {
-        LSUP_rc add_rc = LSUP_graph_add_iter (add_it, &spo);
-        LSUP_triple_done (&spo);
-        if (LIKELY (add_rc == LSUP_OK)) rc = LSUP_OK;
-        else if (add_rc < 0) {
-            rc = add_rc;
-            break;
-        }
-    }
-
-    LSUP_graph_add_done (add_it);
-    LSUP_graph_iter_free (it);
-
-    return rc;
-}
+graph_iter_next_buffer (LSUP_GraphIterator *it, LSUP_BufferTriple *sspo)
+{ return it->store->sif->lu_next_fn (it->data, sspo, NULL); }
 
 
 /**

+ 128 - 144
src/store_htable.c

@@ -11,24 +11,25 @@ typedef bool (*LSUP_key_eq_fn_t)(
 
 
 typedef struct idx_entry_t {
-    LSUP_Key            key;        ///> Serialized term key.
-    LSUP_Buffer *       sterm;      ///> Serialized term.
+    LSUP_Key            key;        ///< Serialized term key.
+    void *              data;       ///< Serialized term data.
+    size_t              size;       ///< Serialized term size.
 } IndexEntry;
 
 typedef struct ht_store_t {
-    struct hashmap *    keys;       ///> Triple keys (set).
-    struct hashmap *    idx;        ///> Map of keys to serialized terms.
+    struct hashmap *    keys;       ///< Triple keys (set).
+    struct hashmap *    idx;        ///< Map of keys to serialized terms.
 } HTStore;
 
 typedef struct ht_iterator_t {
-    HTStore *           store;      ///> Store being iterated.
-    size_t              cur;        ///> Internal hash table cursor.
-    LSUP_Key            luk[3];     ///> 0÷3 lookup keys.
-    LSUP_key_eq_fn_t    eq_fn;      ///> Equality function to test triples.
-    int                 rc;         ///> Return code for *next* result.
-                                    ///> When the end of results is reached,
-                                    ///> this is set to LSUP_END.
-    LSUP_TripleKey *    entry;      ///> Retrieved SPO key.
+    HTStore *           store;      ///< Store being iterated.
+    size_t              cur;        ///< Internal hash table cursor.
+    LSUP_Key            luk[3];     ///< 0÷3 lookup keys.
+    LSUP_key_eq_fn_t    eq_fn;      ///< Equality function to test triples.
+    int                 rc;         ///< Return code for *next* result.
+                                    ///< When the end of results is reached,
+                                    ///< this is set to LSUP_END.
+    LSUP_TripleKey *    entry;      ///< Retrieved SPO key.
 } HTIterator;
 
 
@@ -119,7 +120,7 @@ static int htstore_idx_cmp_fn (const void *a, const void *b, void *udata)
  * Delete callback function for hashmap.
  */
 static void htstore_idx_free_fn (void *item)
-{ LSUP_buffer_free (((IndexEntry *) item)->sterm); }
+{ free (((IndexEntry *) item)->data); }
 
 
 /* * * Other prototypes. * * */
@@ -141,10 +142,22 @@ static LSUP_rc
 htiter_next_key (HTIterator *it);
 
 
-/* * * API * * */
+/*
+ * Interface members.
+ */
 
-HTStore *
-LSUP_htstore_new (const size_t size)
+/** @brief Create a store for an individual graph.
+ *
+ * @param[in] id Graph identifier. This may or may not be set. The store does
+ *  not use this value internally, and does not check for duplicates.
+ *
+ * @param[in] size Initial size of the store (in number of triples). It may be
+ *  0.
+ *
+ * @return New graph store.
+ */
+void *
+htstore_new (const char *id, size_t size)
 {
     HTStore *ht;
     CALLOC_GUARD (ht, NULL);
@@ -163,23 +176,9 @@ LSUP_htstore_new (const size_t size)
 }
 
 
-HTStore *
-LSUP_htstore_copy (const HTStore *src)
-{
-    HTStore *dest = LSUP_htstore_new (LSUP_htstore_size (src));
-    if (UNLIKELY (!dest)) return NULL;
-
-    if (UNLIKELY (LSUP_htstore_copy_contents (dest, src) < 0)) {
-        LSUP_htstore_free (dest);
-        return NULL;
-    }
-
-    return dest;
-}
-
-
+#if 0
 LSUP_rc
-LSUP_htstore_copy_contents (HTStore *dest, const HTStore *src)
+htstore_copy_contents (HTStore *dest, const HTStore *src)
 {
     size_t i = 0;
     LSUP_TripleKey *spok;
@@ -196,88 +195,38 @@ LSUP_htstore_copy_contents (HTStore *dest, const HTStore *src)
 
     return LSUP_OK;
 }
-
-
-HTStore *
-LSUP_htstore_bool_op(
-        const LSUP_bool_op op, const HTStore *s1, const HTStore *s2)
-{
-    if (UNLIKELY (
-            op != LSUP_BOOL_UNION
-            && op != LSUP_BOOL_SUBTRACTION
-            && op != LSUP_BOOL_INTERSECTION
-            && op != LSUP_BOOL_XOR)) {
-        log_error ("Invalid boolean operation.");
-
-        return NULL;
-    }
-
-    HTStore *dest = LSUP_htstore_new (0);
-
-    if (op == LSUP_BOOL_UNION) {
-        dest = LSUP_htstore_copy (s1);
-        if (UNLIKELY (!dest) || LSUP_htstore_copy_contents (dest, s2) < 0)
-            goto fail;
-
-        return dest;
-    }
-
-    LSUP_TripleKey *src_tkey;
-
-    LSUP_HTIterator *it = LSUP_htstore_add_init(dest);
-
-    size_t i = 0;
-    if (op == LSUP_BOOL_XOR) {
-        // Add triples from s2 if not found in s1.
-        while (hashmap_iter (s2->keys, &i, (void **) &src_tkey)) {
-            if (!hashmap_get (s1->keys, src_tkey))
-                htstore_add_key_iter (it, *src_tkey);
-        }
-    }
-
-    i = 0;
-    while (hashmap_iter (s1->keys, &i, (void **) &src_tkey)) {
-        // For XOR and subtraction, add if not found.
-        // For intersection, add if found.
-        if (
-            (op == LSUP_BOOL_INTERSECTION)
-            ^ (hashmap_get (s2->keys, src_tkey) == NULL)
-        )
-            htstore_add_key_iter (it, *src_tkey);
-    }
-    LSUP_htstore_add_done (it);
-
-    return dest;
-
-fail:
-    LSUP_htstore_free (dest);
-    return NULL;
-}
+#endif
 
 
 void
-LSUP_htstore_free (HTStore *ht)
+htstore_free (void *h)
 {
-    hashmap_free (ht->idx);
-    hashmap_free (ht->keys);
-    free (ht);
+    HTStore *store = h;
+    hashmap_free (store->idx);
+    hashmap_free (store->keys);
+    free (store);
 }
 
 
 size_t
-LSUP_htstore_size (const LSUP_HTStore *ht)
-{ return hashmap_count (ht->keys); }
+htstore_size (const void *h)
+{
+    const HTStore *store = h;
+    return hashmap_count (store->keys);
+}
 
 
 LSUP_rc
-LSUP_htstore_add_term (HTStore *store, const LSUP_Buffer *sterm)
+htstore_add_term (void *h, const LSUP_Buffer *sterm)
 {
+    HTStore *store = h;
     IndexEntry entry_s = {
         .key = LSUP_buffer_hash (sterm),
     };
     if (hashmap_get (store->idx, &entry_s)) return LSUP_NOACTION;
 
-    entry_s.sterm = LSUP_buffer_new (sterm->size, sterm->addr);
+    memcpy (entry_s.data, sterm->addr, sterm->size);
+    entry_s.size = sterm->size;
 
     log_trace ("Adding term key: %lx", entry_s.key);
     hashmap_set (store->idx, &entry_s);
@@ -287,9 +236,11 @@ LSUP_htstore_add_term (HTStore *store, const LSUP_Buffer *sterm)
 }
 
 
-LSUP_HTIterator *
-LSUP_htstore_add_init (HTStore *store)
+void *
+htstore_add_init (void *h, const LSUP_Buffer *_unused)
 {
+    (void) _unused;
+    HTStore *store = h;
     HTIterator *it;
     MALLOC_GUARD (it, NULL);
 
@@ -300,8 +251,9 @@ LSUP_htstore_add_init (HTStore *store)
 
 
 LSUP_rc
-LSUP_htstore_add_iter (HTIterator *it, const LSUP_BufferTriple *sspo)
+htstore_add_iter (void *h, const LSUP_BufferTriple *sspo)
 {
+    HTIterator *it = h;
     LSUP_TripleKey spok = {
         LSUP_buffer_hash (sspo->s),
         LSUP_buffer_hash (sspo->p),
@@ -313,54 +265,27 @@ LSUP_htstore_add_iter (HTIterator *it, const LSUP_BufferTriple *sspo)
     if (rc != LSUP_OK) return rc;
 
     for (int i = 0; i < 3; i++)
-        LSUP_htstore_add_term (it->store, LSUP_btriple_pos (sspo, i));
+        htstore_add_term (it->store, LSUP_btriple_pos (sspo, i));
 
     return rc;
 }
 
 
-void
-LSUP_htstore_add_done (HTIterator *it)
-{ LSUP_htiter_free (it); }
-
-
 LSUP_rc
-LSUP_htstore_remove(
-        LSUP_HTStore *store, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
-        const LSUP_Buffer *so,  size_t *ct_p)
+htstore_add_done (void *h)
 {
-    size_t ct;
-
-    LSUP_HTIterator *it = LSUP_htstore_lookup (store, ss, sp, so, &ct);
-    if (UNLIKELY (!it)) return LSUP_DB_ERR;
-
-    LSUP_rc rc;
-    if (ct == 0) {
-        rc = LSUP_NOACTION;
-        goto finally;
-    }
-
-    while (htiter_next_key (it) == LSUP_OK) {
-        log_trace (
-                "Deleting {%lx, %lx, %lx}.",
-                it->entry[0][0], it->entry[0][1], it->entry[0][2]);
-        hashmap_delete (store->keys, it->entry);
-        rc = LSUP_OK;
-        it->cur = 0; // Reset cursor, buckets are rearranged after deletion.
-    }
-
-finally:
-    LSUP_htiter_free (it);
-    if (ct_p) *ct_p = ct;
-
-    return rc;
+    free (h);
+    return LSUP_OK;
 }
 
 
-HTIterator *
-LSUP_htstore_lookup (HTStore *store, const LSUP_Buffer *ss,
-        const LSUP_Buffer *sp, const LSUP_Buffer *so, size_t *ct)
+void *
+htstore_lookup (
+        void *h,
+        const LSUP_Buffer *ss, const LSUP_Buffer *sp, const LSUP_Buffer *so,
+        const LSUP_Buffer *sc, size_t *ct)
 {
+    HTStore *store = h;
     HTIterator *it;
     CALLOC_GUARD (it, NULL);
 
@@ -423,9 +348,40 @@ LSUP_htstore_lookup (HTStore *store, const LSUP_Buffer *ss,
 }
 
 
-void
-LSUP_htiter_free (LSUP_HTIterator *it)
-{ free (it); }
+LSUP_rc
+htstore_remove(
+        void *h, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
+        const LSUP_Buffer *so,  const LSUP_Buffer *_unused, size_t *ct_p)
+{
+    (void) _unused;
+    HTStore *store = h;
+    size_t ct;
+
+    HTIterator *it = htstore_lookup (store, ss, sp, so, NULL, &ct);
+    if (UNLIKELY (!it)) return LSUP_DB_ERR;
+
+    LSUP_rc rc;
+    if (ct == 0) {
+        rc = LSUP_NOACTION;
+        goto finally;
+    }
+
+    while (htiter_next_key (it) == LSUP_OK) {
+        log_trace (
+                "Deleting {%lx, %lx, %lx}.",
+                it->entry[0][0], it->entry[0][1], it->entry[0][2]);
+        hashmap_delete (store->keys, it->entry);
+        rc = LSUP_OK;
+        it->cur = 0; // Reset cursor, buckets are rearranged after deletion.
+    }
+
+finally:
+    free (it);
+    if (ct_p) *ct_p = ct;
+
+    return rc;
+}
+
 
 LSUP_rc
 htiter_next_key (HTIterator *it)
@@ -457,8 +413,10 @@ htiter_next_key (HTIterator *it)
 
 
 LSUP_rc
-LSUP_htiter_next (HTIterator *it, LSUP_BufferTriple *sspo)
+htiter_next (void *h, LSUP_BufferTriple *sspo, LSUP_Buffer **_unused)
 {
+    (void) _unused;
+    HTIterator *it = h;
     LSUP_rc rc = htiter_next_key (it);
     if (rc != LSUP_OK) return rc;
 
@@ -466,6 +424,29 @@ LSUP_htiter_next (HTIterator *it, LSUP_BufferTriple *sspo)
 }
 
 
+const LSUP_StoreInt htstore_int = {
+    .name           = "Hash Table Store",
+    .features       = LSUP_STORE_COW,
+
+    .setup_fn       = NULL,
+    .new_fn         = htstore_new,
+    .free_fn        = htstore_free,
+
+    .size_fn        = htstore_size,
+
+    .add_init_fn    = htstore_add_init,
+    .add_iter_fn    = htstore_add_iter,
+    .add_done_fn    = htstore_add_done,
+    .add_term_fn    = htstore_add_term,
+
+    .lookup_fn      = htstore_lookup,
+    .lu_next_fn     = htiter_next,
+    .lu_free_fn     = free,
+
+    .remove_fn      = htstore_remove,
+};
+
+
 /* * * Statics * * */
 
 inline static LSUP_rc
@@ -477,15 +458,18 @@ tkey_to_strp (
 
     tmp = hashmap_get (store->idx, spok + 0);
     if (UNLIKELY (!tmp)) return LSUP_DB_ERR;
-    sspo->s = tmp->sterm;
+    sspo->s->addr = tmp->data;
+    sspo->s->size = tmp->size;
 
     tmp = hashmap_get (store->idx, spok + 1);
     if (UNLIKELY (!tmp)) return LSUP_DB_ERR;
-    sspo->p = tmp->sterm;
+    sspo->s->addr = tmp->data;
+    sspo->s->size = tmp->size;
 
     tmp = hashmap_get (store->idx, spok + 2);
     if (UNLIKELY (!tmp)) return LSUP_DB_ERR;
-    sspo->o = tmp->sterm;
+    sspo->o->addr = tmp->data;
+    sspo->o->size = tmp->size;
 
     return LSUP_OK;
 }

+ 64 - 42
src/store_mdb.c

@@ -28,7 +28,6 @@
  */
 
 typedef char DbLabel[8];
-typedef struct mdbstore_t MDBStore;
 typedef struct mdbstore_iter_t MDBIterator;
 
 typedef enum {
@@ -42,14 +41,14 @@ typedef enum {
     OP_REMOVE,
 } StoreOp;
 
-struct mdbstore_t {
-    MDB_env *           env;            // Environment handle.
-    MDB_txn *           txn;            // Current transaction.
-    MDB_dbi             dbi[N_DB];      // DB handles. Refer to DbIdx enum.
-    LSUP_Buffer *       default_ctx;    // Default ctx as a serialized URI.
-    StoreState          state;          // Store state.
-    int                 features;       // Store feature flags.
-};
+typedef struct mdbstore_t {
+    MDB_env *           env;            ///< Environment handle.
+    MDB_txn *           txn;            ///< Current transaction.
+    MDB_dbi             dbi[N_DB];      ///< DB handles. Refer to DbIdx enum.
+    LSUP_Buffer *       default_ctx;    ///< Default ctx as a serialized URI.
+    StoreState          state;          ///< Store state.
+    int                 features;       ///< Store feature flags.
+} MDBStore;
 
 /** @brief Iterator operation.
  *
@@ -65,7 +64,7 @@ typedef void (*iter_op_fn_t)(MDBIterator *it);
 
 
 /// Triple iterator.
-struct mdbstore_iter_t {
+typedef struct mdbstore_iter_t {
     MDBStore *          store;      ///< MDB store handle.
     MDB_txn *           txn;        ///< MDB transaction.
     MDB_cursor *        cur;        ///< MDB cursor.
@@ -82,7 +81,7 @@ struct mdbstore_iter_t {
     size_t              ct;         ///< Current count of records inserted or
                                     ///< results found.
     int                 rc;         ///< MDB_* return code for the next result.
-};
+} MDBIterator;
 
 
 /*
@@ -312,18 +311,28 @@ loop_end:
  *
  * This function takes care of creaating the environment path if not existing,
  * and checking that it's a writable directory. If the path is not specified
- * in the LSUP_STORE_PATH environment variable, a default directory is used.
+ * in the LSUP_MDB_STORE_URN environment variable, a default directory is used.
  */
 LSUP_rc
-mdbstore_setup (const char *path, bool clear)
+mdbstore_setup (const char *id, bool clear)
 {
     int rc;
 
     // Set environment path.
-    if (!path) {
-        log_error("Path %s does not exist.", path);
-        return LSUP_ERROR;
+    if (!id) id = getenv ("LSUP_MDB_STORE_URN");
+    if (!id) {
+        id = LSUP_MDB_STORE_URN;
+        log_info (
+            "`LSUP_MDB_STORE_URN' environment variable is not "
+            "set. The default location %s will be used as the store URN.", id
+        );
+    }
+    if (strcmp ("file://", id) != 0) {
+        log_error (
+                "MDB store ID must be in the `file://<abs_path>` format.");
+        return LSUP_VALUE_ERR;
     }
+    const char *path = id + 7;
 
     // TODO Verify that a writable directory exists or can be created.
     //struct stat path_stat;
@@ -366,8 +375,16 @@ mdbstore_setup (const char *path, bool clear)
  *   and disk constraints. The default map size is 1Tb.
  */
 void *
-mdbstore_new (const char *path, const LSUP_Buffer *default_ctx)
+mdbstore_new (const char *id, size_t _unused)
 {
+    (void) _unused;
+    if (strcmp ("file://", id) != 0) {
+        log_error (
+                "MDB store ID must be in the `file://<abs_path>` format.");
+        return NULL;
+    }
+    const char *path = id + 7;
+
     int db_rc;
     MDBStore *store;
     MALLOC_GUARD (store, NULL);
@@ -375,9 +392,12 @@ mdbstore_new (const char *path, const LSUP_Buffer *default_ctx)
     db_rc = mdb_env_create (&store->env);
     log_trace ("create rc: %d", db_rc);
 
+    // TODO Remove this? LSUP_default_ctx_buf is always present.
     store->default_ctx = (
-            default_ctx ?
-            LSUP_buffer_new (default_ctx->size, default_ctx->addr) : NULL);
+            LSUP_default_ctx ?
+            LSUP_buffer_new (
+                LSUP_default_ctx_buf->size, LSUP_default_ctx_buf->addr
+            ) : NULL);
 
     // Set map size.
     size_t mapsize;
@@ -410,16 +430,8 @@ mdbstore_new (const char *path, const LSUP_Buffer *default_ctx)
 
         if (stat.ms_entries == 0) {
             log_debug ("Loading initial data into %s", path);
-
             // Load initial NS map.
-            LSUP_NSMap *nsm = LSUP_nsmap_new();
-
-            for (int i = 0; init_nsmap[i][0] != NULL; i++)
-                LSUP_nsmap_add (nsm, init_nsmap[i][0], init_nsmap[i][1]);
-
-            mdbstore_nsm_put (store, nsm);
-
-            LSUP_nsmap_free (nsm);
+            mdbstore_nsm_put (store, LSUP_default_nsm);
         }
     }
 
@@ -456,12 +468,18 @@ mdbstore_free (void *h)
 }
 
 
-int mdbstore_features (MDBStore *store)
-{ return store->features; }
+char *mdbstore_id (const void *h)
+{
+    const MDBStore *store = h;
+    const char *path;
+    mdb_env_get_path (store->env, &path);
+
+    return strcat ("file://", path);
+}
 
 
 LSUP_rc
-mdbstore_stat (MDBStore *store, MDB_stat *stat)
+mdbstore_stat (const MDBStore *store, MDB_stat *stat)
 {
     if (!(store->state & LSSTORE_INIT)) return 0;
 
@@ -477,9 +495,9 @@ mdbstore_stat (MDBStore *store, MDB_stat *stat)
 
 
 size_t
-mdbstore_size (void *h)
+mdbstore_size (const void *h)
 {
-    MDBStore *store = h;
+    const MDBStore *store = h;
     // Size is calculated outside of any pending write txn.
 
     MDB_stat stat;
@@ -565,7 +583,7 @@ mdbstore_add_iter (void *h, const LSUP_BufferTriple *sspo)
         if (db_rc != MDB_SUCCESS && db_rc != MDB_KEYEXIST) {
             log_error (
                     "MDB error while inserting term: %s",
-                    LSUP_strerror(db_rc));
+                    LSUP_strerror (db_rc));
             return LSUP_DB_ERR;
         }
     }
@@ -679,8 +697,16 @@ key_to_sterm (MDBIterator *it, const LSUP_Key key, LSUP_Buffer *sterm)
 }
 
 
+bool
+mdbstore_trp_exist (
+        void *h, const LSUP_BufferTriple *sspo, const LSUP_Buffer *sc) 
+{
+    MDBStore *store = h;
+}
+
+
 void *
-mdbstore_lookup(
+mdbstore_lookup (
         void *h, const LSUP_Buffer *ss, const LSUP_Buffer *sp,
         const LSUP_Buffer *so, const LSUP_Buffer *sc, size_t *ct)
 {
@@ -1088,7 +1114,7 @@ fail:
 const LSUP_StoreInt mdbstore_int = {
     .name           = "MDB Store",
     .features       = LSUP_STORE_PERM | LSUP_STORE_CTX | LSUP_STORE_IDX
-                      | LSUP_STORE_TXN,
+                      | LSUP_STORE_TXN | LSUP_STORE_COW,
 
     .setup_fn       = mdbstore_setup,
     .new_fn         = mdbstore_new,
@@ -1103,6 +1129,8 @@ const LSUP_StoreInt mdbstore_int = {
     .add_term_fn    = mdbstore_add_term,
 
     .lookup_fn      = mdbstore_lookup,
+    .lu_next_fn     = mdbiter_next,
+    .lu_free_fn     = mdbiter_free,
 
     .remove_fn      = mdbstore_remove,
 
@@ -1110,12 +1138,6 @@ const LSUP_StoreInt mdbstore_int = {
     .nsm_get_fn     = mdbstore_nsm_get,
 };
 
-const LSUP_StoreIteratorInt mdbiter_int = {
-    .name           = "MDB Iterator",
-    .iter_fn        = mdbiter_next,
-    .free_fn        = mdbiter_free,
-};
-
 
 /* * * Static functions. * * */
 

+ 1 - 1
test/test_store_ht.c

@@ -55,7 +55,7 @@ static int test_htstore()
         1, 0,
     };
 
-    LSUP_BufferTriple *sspo = STRP_DUMMY;
+    LSUP_BufferTriple *sspo = BTRP_DUMMY;
     for (int i = 0; i < NUM_TRP; i++) {
         size_t ct, ct2 = 0;
         log_info ("Testing triple lookup #%d.", i);