Browse Source

Build test for, and fix, store add op.

Stefano Cossu 4 years ago
parent
commit
11107a4cbf
13 changed files with 382 additions and 215 deletions
  1. 3 2
      README.md
  2. 12 15
      include/core.h
  3. 11 4
      include/store_mdb.h
  4. 48 0
      include/triple.h
  5. 40 0
      src/core.c
  6. 0 1
      src/graph.c
  7. 103 119
      src/store_mdb.c
  8. 8 0
      src/triple.c
  9. 7 1
      test.c
  10. 78 0
      test/assets.h
  11. 22 0
      test/test.h
  12. 3 73
      test/test_graph.c
  13. 47 0
      test/test_store_mdb.c

+ 3 - 2
README.md

@@ -41,10 +41,11 @@ of features as a standalone library:
 
 ## Possibly In scope – Long Term
 
-- SPARQL queries (We'll see... Will definitely need help)
 - Binary serialization and hashing of graphs
+- Binary protocol for synchronizing remote replicas
 - Lua bindings
-- C++ bindings (requires external contribution)
+- SPARQL queries (We'll see... Will definitely need help)
+- C++ compatibility (requires external contribution and use cases)
 
 ## Likely Out of Scope
 

+ 12 - 15
include/core.h

@@ -2,12 +2,15 @@
 #define _LSUP_CORE_H
 
 #include <ctype.h>
+#include <errno.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <uuid/uuid.h>
 
 #ifdef DEBUG
@@ -76,23 +79,17 @@ inline int min(int x, int y) { return x < y ? x : y; }
 inline int max(int x, int y) { return x > y ? x : y; }
 
 
+/** @brief Make recursive directories.
+ *
+ * from https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950
+ */
+LSUP_rc mkdir_p(const char *path, mode_t mode);
+
+
 // Error handling via goto.
-#define CHECK(exp, marker) rc = (exp); if (rc != LSUP_OK) goto marker
+#define CHECK(exp, rc, marker) (rc) = (exp); if ((rc) != LSUP_OK) goto marker
 
 // Jump if rc is negative (skip warnings).
-#define PCHECK(exp, marker) rc = (exp); if (rc < LSUP_OK) goto marker
-
-// Check against a list of allowed return codes.
-// If none match, jump to marker.
-#define MCHECK(exp, allowed, marker)                                    \
-    rc = (exp);                                                         \
-    bool jump = true;                                                   \
-    for(int i = 0; i < sizeof(allowed) / sizeof(int); i++) {            \
-        if (rc == allowed[i]) {                                         \
-            jump = false;                                               \
-            break;                                                      \
-        }                                                               \
-    }                                                                   \
-    if (jump) goto marker;                                              \
+#define PCHECK(exp, rc, marker) (rc) = (exp); if ((rc) < LSUP_OK) goto marker
 
 #endif

+ 11 - 4
include/store_mdb.h

@@ -19,7 +19,7 @@
 #define _LSUP_STORE_MDB_H
 
 #include "lmdb.h"
-#include "term.h"
+#include "triple.h"
 
 typedef char DbLabel[8];
 typedef struct MDBStore LSUP_MDBStore;
@@ -65,8 +65,15 @@ LSUP_rc LSUP_store_setup(char **path/*, bool clear*/);
  *  triples inserted without a context specified. If NULL, the store operates
  *  in triple mode.
  */
-LSUP_rc LSUP_store_open(
-        LSUP_MDBStore *store, const char *path, LSUP_Buffer *default_ctx);
+LSUP_MDBStore *LSUP_store_init(const char *path, LSUP_Buffer *default_ctx);
+
+
+/** @brief Close a store and free its handle.
+ *
+ * @param[in] store Store pointer.
+ *
+ */
+void LSUP_store_done(LSUP_MDBStore *store);
 
 
 /** @brief Print stats about a store and its databases.
@@ -103,7 +110,7 @@ size_t LSUP_store_size(LSUP_MDBStore *store);
  */
 LSUP_rc LSUP_store_add(
         struct MDBStore *store, const LSUP_Buffer *sc,
-        const LSUP_SerTerm **data, const size_t data_size);
+        const LSUP_SerTriple *data, const size_t data_size);
 
 
 /** @brief Perform an arbitraty function on matching triples and context.

+ 48 - 0
include/triple.h

@@ -1,3 +1,6 @@
+#ifndef _LSUP_TRIPLE_H
+#define _LSUP_TRIPLE_H
+
 #include "term.h"
 
 typedef struct LSUP_Triple {
@@ -12,4 +15,49 @@ typedef struct LSUP_SerTriple {
     LSUP_SerTerm *o;
 } LSUP_SerTriple;
 
+typedef enum {
+    TRP_POS_S = 0,
+    TRP_POS_P = 1,
+    TRP_POS_O = 2,
+} LSUP_TriplePos;
+
+
+#define _FN_BODY \
+    if (n == 0) return trp->s; \
+    if (n == 1) return trp->p; \
+    if (n == 2) return trp->o; \
+    return NULL;
+
+
+/** @brief Get triple by term position.
+ *
+ * Useful for looping over all terms.
+ *
+ * @param trp[in] Triple pointer.
+ *
+ * @param n[in] A number between 0÷2.
+ *
+ * @return Corresponding triple term or NULL if n is out of range.
+ */
+inline LSUP_Term *
+LSUP_triple_term_by_pos(const LSUP_Triple *trp, LSUP_TriplePos n)
+{ _FN_BODY }
+
+
+/** @brief Get serialized triple by term position.
+ *
+ * Useful for looping over all terms.
+ *
+ * @param trp[in] Serialized triple pointer.
+ *
+ * @param n[in] A number between 0÷2.
+ *
+ * @return Corresponding serialized term or NULL if n is out of range.
+ */
+inline LSUP_SerTerm *
+LSUP_ser_triple_term_by_pos(const LSUP_SerTriple *trp, LSUP_TriplePos n)
+{ _FN_BODY }
+
+
 // TODO Add constructors and destructors with term type checks.
+#endif

+ 40 - 0
src/core.c

@@ -3,3 +3,43 @@
 // Extern inline prototypes.
 int min(int x, int y);
 int max(int x, int y);
+
+
+int mkdir_p(const char *path, mode_t mode)
+{
+    /* Adapted from http://stackoverflow.com/a/2336245/119527 */
+    const size_t len = strlen(path);
+    char _path[PATH_MAX];
+    char *p;
+
+    errno = 0;
+
+    /* Copy string so its mutable */
+    if (len > sizeof(_path)-1) {
+        errno = ENAMETOOLONG;
+        return -1;
+    }
+    strcpy(_path, path);
+
+    /* Iterate the string */
+    for (p = _path + 1; *p; p++) {
+        if (*p == '/') {
+            /* Temporarily truncate */
+            *p = '\0';
+
+            if (mkdir(_path, mode) != 0) {
+                if (errno != EEXIST)
+                    return -1;
+            }
+
+            *p = '/';
+        }
+    }
+
+    if (mkdir(_path, mode) != 0) {
+        if (errno != EEXIST)
+            return -1;
+    }
+
+    return 0;
+}

+ 0 - 1
src/graph.c

@@ -21,7 +21,6 @@ typedef enum KSetFlag {
  * Static handles.
  */
 static const char *default_ctx_label = "urn:lsup:default";
-static LSUP_Buffer *default_ctx = NULL; // Default context URI for quad store.
 
 
 /**

+ 103 - 119
src/store_mdb.c

@@ -1,5 +1,3 @@
-#include <sys/stat.h>
-#include <errno.h>
 #include <ftw.h>
 
 #include "store_mdb.h"
@@ -19,6 +17,16 @@
  */
 #define N_DB 12
 
+/**
+ * Memory map size.
+ */
+#if !(defined __LP64__ || defined __LLP64__) || \
+        defined _WIN32 && !defined _WIN64
+    #define DEFAULT_MAPSIZE 1<<31 // 2Gb (limit for 32-bit systems)
+#else
+    #define DEFAULT_MAPSIZE 1UL<<40 // 1Tb
+#endif
+
 #define DEFAULT_ENV_PATH "./mdb_store"
 #define ENV_DIR_MODE 0750
 #define ENV_FILE_MODE 0640
@@ -31,7 +39,6 @@ typedef char DbLabel[8];
 typedef enum {
     LSSTORE_INIT         = 1, // Is the store environment set up on disk?
     LSSTORE_OPEN         = 3, // Is the environment open? Assumes init is set.
-    LSSTORE_DB_CREATED   = 5, // Are DBs created? Assumes init is set.
 } StoreState;
 
 
@@ -61,6 +68,7 @@ struct MatchArgs {
     void *ctx;
 };
 
+static LSUP_Buffer *default_ctx = NULL; // Default context URI for quad store.
 
 /**
  * Main DBs. These are the master information containers.
@@ -166,7 +174,6 @@ static const uint8_t lookup_ordering_2bound[3][3] = {
 /**
  * Static prototypes.
  */
-static int dbi_init(LSUP_MDBStore *store);
 static int index_triple(
         LSUP_MDBStore *store, StoreOp op,
         LSUP_TripleKey spok, LSUP_Key ck);
@@ -199,44 +206,44 @@ LSUP_store_setup(char **path/*, bool clear*/) // TODO clear
     int rc;
 
     // Set environment path.
-    if (path == NULL || (*path = getenv("LSUP_STORE_PATH")) == NULL) {
-        printf(
-                "WARNING: `LSUP_STORE_PATH' environment variable is not set. "
-                "A local directory will be used to store the LSUP data.");
+    if (path == NULL && (*path = getenv("LSUP_STORE_PATH")) == NULL) {
+        // FIXME This won't work for multiple graphs with different disk
+        // back ends. A random path generator needs to be used.
         *path = DEFAULT_ENV_PATH;
+        fprintf(
+                stderr,
+                "WARNING: `LSUP_STORE_PATH' environment variable is not set. "
+                "The default location %s will be used as the graph store.\n",
+                *path);
     }
 
     // Verify that a writable directory exists or can be created.
     struct stat path_stat;
 
-    rc = stat(*path, &path_stat);
-    if (rc == ENOENT) {
+    /*
+    // TODO clear
+    if (clear) {
+        rmrf(*path);
         if (mkdir(*path, ENV_DIR_MODE) != 0) abort();
-
-    } else if (!S_ISDIR(path_stat.st_mode)) {
-        printf("%s is not a valid directory.\n", path);
-        abort();
-
-    /* TODO clear
-    } else {
-        if (clear) {
-            rmrf(*path);
-            if (mkdir(*path, ENV_DIR_MODE) != 0) abort();
-        }
-    */
     }
+    */
+
+    if (mkdir_p(*path, ENV_DIR_MODE) != 0) abort();
 
     // Open a temporary environment and txn to create the DBs.
     MDB_env *env;
     mdb_env_create(&env);
+
+    mdb_env_set_maxdbs(env, N_DB);
     mdb_env_open(env, *path, 0, ENV_FILE_MODE);
 
     MDB_txn *txn;
     mdb_txn_begin(env, NULL, 0, &txn);
     for (int i = 0; i < N_DB; i++) {
+        TRACE("Creating DB %s", db_labels[i]);
         MDB_dbi dbi;
-        mdb_dbi_open(txn, db_labels[i], db_flags[i] | MDB_CREATE, &dbi);
-        mdb_dbi_close(env, dbi);
+        rc = mdb_dbi_open(txn, db_labels[i], db_flags[i] | MDB_CREATE, &dbi);
+        if (rc != MDB_SUCCESS) return rc;
     }
 
     mdb_txn_commit(txn);
@@ -246,11 +253,15 @@ LSUP_store_setup(char **path/*, bool clear*/) // TODO clear
 }
 
 
-LSUP_rc
-LSUP_store_open(
-        LSUP_MDBStore *store, const char *path, LSUP_Buffer *default_ctx)
+LSUP_MDBStore *
+LSUP_store_init(const char *path, LSUP_Buffer *default_ctx)
 {
-    if(store->state & LSSTORE_OPEN) return LSUP_NOACTION;
+    int rc;
+    LSUP_MDBStore *store;
+    CRITICAL(store = malloc(sizeof(LSUP_MDBStore)));
+
+    rc = mdb_env_create(&store->env);
+    TRACE("create rc: %d", rc);
 
     if (default_ctx == NULL) store->default_ctx = NULL;
     else {
@@ -263,29 +274,37 @@ LSUP_store_open(
     char *env_mapsize = getenv("LSUP_MDB_MAPSIZE");
 
     if (env_mapsize == NULL) {
-        mapsize = 0x10000000000; // 1Tb
+        mapsize = DEFAULT_MAPSIZE;
     } else {
         sscanf(env_mapsize, "%lu", &mapsize);
     }
-    mdb_env_set_mapsize(store->env, mapsize);
+    TRACE("mapsize rc: %d", rc);
+    if(rc != MDB_SUCCESS) return NULL;
 
-    mdb_env_set_maxdbs(store->env, N_DB);
+    rc = mdb_env_set_maxdbs(store->env, N_DB);
+    if(rc != MDB_SUCCESS) return NULL;
 
-    int rc = mdb_env_open(store->env, path, 0, ENV_FILE_MODE);
+    rc = mdb_env_open(store->env, path, 0, ENV_FILE_MODE);
+    if (rc != MDB_SUCCESS) return NULL;
 
     // Assign DB handles to store->dbi.
     MDB_txn *txn;
     mdb_txn_begin(store->env, NULL, 0, &txn);
     for (int i = 0; i < N_DB; i++) {
-        mdb_dbi_open(
+        rc = mdb_dbi_open(
                 txn, db_labels[i], db_flags[i], store->dbi + i);
+        if (rc != MDB_SUCCESS) {
+            mdb_txn_abort(txn);
+            return NULL;
+        }
     }
 
     mdb_txn_commit(txn);
 
-    store->state = LSSTORE_OPEN;
+    store->state |= LSSTORE_OPEN;
+    store->txn = NULL;
 
-    return rc;
+    return store;
 }
 
 
@@ -313,14 +332,13 @@ LSUP_store_size(LSUP_MDBStore *store)
 LSUP_rc
 LSUP_store_add(
         LSUP_MDBStore *store, const LSUP_Buffer *sc,
-        const LSUP_SerTerm **data, const size_t data_size)
+        const LSUP_SerTriple *data, const size_t data_size)
 {
-
     MDB_val key_v, data_v;
 
     bool txn_pending = false;
     if (!store->txn) {
-        mdb_txn_begin(store->env, NULL, MDB_RDONLY, &store->txn);
+        mdb_txn_begin(store->env, NULL, 0, &store->txn);
         txn_pending = true;
     }
 
@@ -334,7 +352,7 @@ LSUP_store_add(
         ck = LSUP_sterm_to_key(sc);
 
         // Insert t:st for context.
-        TRACE("Adding context: %s", sc);
+        //TRACE("Adding context: %s", sc);
         key_v.mv_data = &ck;
         key_v.mv_size = KLEN;
         data_v.mv_data = sc->addr;
@@ -345,74 +363,62 @@ LSUP_store_add(
                 &key_v, &data_v, MDB_NOOVERWRITE);
     }
 
-    int rc = LSUP_NOACTION;
-    for (size_t i = 0; i < data_size && rc == LSUP_OK; i++) {
-        const LSUP_SerTerm *sspo = data[i];
-        /*
-        LSUP_SerTerm sspo[3];
-
-        CHECK(LSUP_term_serialize(spo->s, sspo), _add_free_sterm1);
-        CHECK(LSUP_term_serialize(spo->p, sspo + 1), _add_free_sterm2);
-        CHECK(LSUP_term_serialize(spo->s, sspo + 2), _add_free_sterms);
-        */
-
+    LSUP_rc rc = LSUP_NOACTION;
+    int db_rc;
+    for (size_t i = 0; i < data_size; i++) {
+        const LSUP_SerTriple *sspo = data + i;
         LSUP_TripleKey spok = NULL_TRP;
 
         // Add triple.
-        TRACE("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
-
-        int put_rc[2] = {LSUP_OK, MDB_KEYEXIST};
-        // Insert t:st for s, p, o
         for (int j = 0; j < 3; j++) {
-            spok[i] = LSUP_sterm_to_key(sspo + i);
+            LSUP_SerTerm *st = LSUP_ser_triple_term_by_pos(sspo, j);
+
+            printf("Inserting term: ");
+            LSUP_buffer_print(st);
+            printf("\n");
 
-            key_v.mv_data = spok + i;
+            spok[j] = LSUP_sterm_to_key(st);
+
+            key_v.mv_data = spok + j;
             key_v.mv_size = KLEN;
-            data_v.mv_data = (sspo + i)->addr;
-            data_v.mv_size = (sspo + i)->size;
-
-            MCHECK(
-                    mdb_put(
-                            store->txn, store->dbi[IDX_T_ST],
-                            &key_v, &data_v, MDB_NOOVERWRITE),
-                    put_rc, _add_close_txn);
+            data_v.mv_data = st->addr;
+            data_v.mv_size = st->size;
+
+            db_rc = mdb_put(
+                    store->txn, store->dbi[IDX_T_ST],
+                    &key_v, &data_v, MDB_NOOVERWRITE);
+            if (db_rc == MDB_SUCCESS) rc = LSUP_OK;
+            else if (db_rc != MDB_KEYEXIST) goto _add_close_txn;
         }
 
-        // Insert spo:c
+        TRACE("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
+
+        // Insert spo:c.
         key_v.mv_data = spok;
         key_v.mv_size = TRP_KLEN;
-        // In triple mode, data is empty.
-        data_v.mv_data = ck == NULL_KEY ? NULL : &ck;
+
+        // In triple mode, data is empty (= NULL_KEY).
+        data_v.mv_data = &ck;
         data_v.mv_size = ck == NULL_KEY ? 0 : KLEN;
 
-        MCHECK(
-                mdb_put(
-                    store->txn, store->dbi[IDX_SPO_C],
-                    &key_v, &data_v, MDB_NODUPDATA),
-                put_rc, _add_close_txn);
 
-        // Index.
-        PCHECK(index_triple(store, OP_ADD, spok, ck), _add_close_txn);
+        db_rc = mdb_put(
+                store->txn, store->dbi[IDX_SPO_C],
+                &key_v, &data_v, MDB_NODUPDATA);
+        if (db_rc == MDB_SUCCESS) rc = LSUP_OK;
+        else if (db_rc != MDB_KEYEXIST) goto _add_close_txn;
 
-        // Free serialized terms.
-/*
-_add_free_sterms:
-        LSUP_buffer_done(sspo + 2);
-_add_free_sterm2:
-        LSUP_buffer_done(sspo + 1);
-_add_free_sterm1:
-        LSUP_buffer_done(sspo);
-*/
+        // Index.
+        PCHECK(index_triple(store, OP_ADD, spok, ck), db_rc, _add_close_txn);
     }
 
 _add_close_txn:
     // Only return commit rc if it fails.
     if (txn_pending) {
-        if (rc >= LSUP_OK) {
-            int txn_rc;
-            if((txn_rc = mdb_txn_commit(store->txn)) != MDB_SUCCESS) {
+        if (rc == LSUP_OK) {
+            if((db_rc = mdb_txn_commit(store->txn)) != MDB_SUCCESS) {
                 mdb_txn_abort(store->txn);
-                rc = txn_rc;
+                rc = db_rc;
             }
         } else mdb_txn_abort(store->txn);
 
@@ -641,34 +647,6 @@ static int rmrf(char *path)
 */
 
 
-/**
- * @brief Open and allocate DB handles in an array.
- *
- * @param bool create [in]: If true, the DBs are created. This is only needed
- *  on bootstrap.
- */
-static LSUP_rc dbi_init(LSUP_MDBStore *store)
-{
-    bool db_created = store->state & LSSTORE_DB_CREATED;
-    MDB_txn *txn;
-    unsigned int txn_flags = db_created ? MDB_RDONLY : 0;
-    unsigned int create_flag = db_created ? 0 : MDB_CREATE;
-
-    mdb_txn_begin(store->env, NULL, txn_flags, &txn);
-
-    for (int i = 0; i < N_DB; i++) {
-        mdb_dbi_open(
-                txn, db_labels[i], db_flags[i] | create_flag, store->dbi + i);
-    }
-
-    store->state |= LSSTORE_DB_CREATED;
-
-    mdb_txn_commit(txn);
-
-    return 0;
-}
-
-
 /* TODO
 inline static int
 check_txn_open(MDB_txn *txn, bool write)
@@ -732,9 +710,10 @@ index_triple(
     v1.mv_size = KLEN;
     v2.mv_size = DBL_KLEN;
 
+    int db_rc;
     for (int i = 0; i < 3; i++) {
-        MDB_dbi db1 = lookup_indices[i];        // s:po, p:so, o:sp
-        MDB_dbi db2 = lookup_indices[i + 3];    // po:s, so:p, sp:o
+        MDB_dbi db1 = store->dbi[lookup_indices[i]];      // s:po, p:so, o:sp
+        MDB_dbi db2 = store->dbi[lookup_indices[i + 3]];  // po:s, so:p, sp:o
 
         v1.mv_data = spok + i;
         v2.mv_data = dbl_keys[i];
@@ -761,11 +740,16 @@ index_triple(
             mdb_cursor_close(cur2);
 
         } else { // OP_ADD is guaranteed.
-            mdb_put(store->txn, db1, &v1, &v2, MDB_NODUPDATA);
-            mdb_put(store->txn, db2, &v2, &v1, MDB_NODUPDATA);
+            CHECK(
+                    mdb_put(store->txn, db1, &v1, &v2, MDB_NODUPDATA),
+                    db_rc, _index_triple_exit);
+            CHECK(
+                    mdb_put(store->txn, db2, &v2, &v1, MDB_NODUPDATA),
+                    db_rc, _index_triple_exit);
         }
     }
 
+_index_triple_exit:
     return rc;
 }
 

+ 8 - 0
src/triple.c

@@ -0,0 +1,8 @@
+#include "triple.h"
+
+// Extern inline prototypes.
+LSUP_Term *LSUP_triple_term_by_pos(
+        const LSUP_Triple *trp, LSUP_TriplePos n);
+LSUP_SerTerm *LSUP_ser_triple_term_by_pos(
+        const LSUP_SerTriple *trp, LSUP_TriplePos n);
+

+ 7 - 1
test.c

@@ -1,10 +1,16 @@
 #include "test_term.c"
 #include "test_htable.c"
+#include "test_store_mdb.c"
 #include "test_graph.c"
 
 int main(int argc, char **argv) {
 
-    int result = (term_tests() | htable_tests() | graph_tests());
+    int result = (
+        //term_tests() |
+        //htable_tests() |
+        store_mdb_test() |
+        //graph_tests() |
+        0);
 
     printf("Test result: %lu\n", (size_t)result);
 

+ 78 - 0
test/assets.h

@@ -0,0 +1,78 @@
+#ifndef _TEST_ASSETS_H
+#define _TEST_ASSETS_H
+
+#include "triple.h"
+
+#define NUM_TRP 10
+
+LSUP_Triple *create_triples()
+{
+    LSUP_Triple *trp;
+    CRITICAL(trp = malloc(NUM_TRP * sizeof(LSUP_Triple)));
+
+    // These constitute overall 10 individual triples, 8 unique.
+
+    trp[0].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
+    trp[0].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:0", NULL, NULL);
+    trp[0].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:0", NULL, NULL);
+
+    trp[1].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:1", NULL, NULL);
+    trp[1].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:1", NULL, NULL);
+    trp[1].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:1", NULL, NULL);
+
+    trp[2].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:2", NULL, NULL);
+    trp[2].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:2", NULL, NULL);
+    trp[2].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:2", NULL, NULL);
+
+    trp[3].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
+    trp[3].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:1", NULL, NULL);
+    trp[3].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:2", NULL, NULL);
+
+    trp[4].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
+    trp[4].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:2", NULL, NULL);
+    trp[4].o = LSUP_term_new(
+            LSUP_TERM_LITERAL, "String 1", NULL, NULL);
+
+    trp[5].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
+    trp[5].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:5", NULL, NULL);
+    trp[5].o = LSUP_term_new(
+            LSUP_TERM_LITERAL, "String 2", "xsd:string", NULL);
+
+    trp[6].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:1", NULL, NULL);
+    trp[6].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:6", NULL, NULL);
+    trp[6].o = LSUP_term_new(
+            LSUP_TERM_LITERAL, "String 3", "xsd:string", "es-ES");
+
+    // Let's reuse pointers. Do not double-free.
+    trp[7].s = trp[0].s; // <urn:s:0>
+    trp[7].p = trp[2].p; // <urn:p:2>
+    trp[7].o = trp[5].o; // "String 2"^^xsd:string
+
+    // Duplicate of trp[7]. Do not double-free.
+    trp[8].s = trp[0].s;
+    trp[8].p = trp[2].p;
+    trp[8].o = trp[5].o;
+
+    // Duplicate of trp[7] from different pointers with same value.
+    // Do not double-free.
+    trp[9].s = trp[5].s;
+    trp[9].p = trp[4].p;
+    trp[9].o = trp[5].o;
+
+    return trp;
+}
+
+
+void free_triples(LSUP_Triple *trp)
+{
+    // Last three triples are second pointers.
+    for(int i=0; i < NUM_TRP - 3; i++) {
+        LSUP_term_free(trp[i].s);
+        LSUP_term_free(trp[i].p);
+        LSUP_term_free(trp[i].o);
+    }
+
+    free(trp);
+}
+#endif
+

+ 22 - 0
test/test.h

@@ -10,6 +10,9 @@
  * Minimal unit testing framework.
  * Inspired from http://www.jera.com/techinfo/jtns/jtn002.html
  */
+
+/** @brief assert true.
+ */
 #define ASSERT(test, msg) do { \
     if (!(test)) {\
         fprintf(\
@@ -19,6 +22,8 @@
     }\
 } while (0)
 
+/** @brief Expect that two int values are equal.
+ */
 #define EXPECT_INT_EQ(got, exp) do { \
     if ((exp) != (got)) {\
         fprintf(\
@@ -28,6 +33,8 @@
     }\
 } while (0)
 
+/** @brief Expect that two string values are equal.
+ */
 #define EXPECT_STR_EQ(got, exp) do { \
     if (strcmp((exp), (got)) != 0) {\
         fprintf(\
@@ -36,6 +43,21 @@
         return -1; \
     }\
 } while (0)
+
+#define EXPECT_PASS(exp) do { \
+    int rc = (exp); \
+    if (rc != LSUP_OK) {\
+        fprintf(\
+                stderr, "!!! Test failed at %s:%d. Error code: %d\n",\
+                __FILE__, __LINE__, rc); \
+        return -1; \
+    }\
+} while (0)
+
+/** @brief Run a test by function name.
+ *
+ * The function must not accept any parameters.
+ */
 #define RUN(test) do { int rc = test(); tests_run++; \
                        if (rc != 0) return -1; } while (0)
 

+ 3 - 73
test/test_graph.c

@@ -1,75 +1,6 @@
 #include "test.h"
 #include "graph.h"
-
-#define NUM_TRP 10
-
-static int _create_triples(LSUP_Triple *trp)
-{
-    // These constitute overall 10 individual triples, 8 unique.
-
-    trp[0].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
-    trp[0].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:0", NULL, NULL);
-    trp[0].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:0", NULL, NULL);
-
-    trp[1].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:1", NULL, NULL);
-    trp[1].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:1", NULL, NULL);
-    trp[1].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:1", NULL, NULL);
-
-    trp[2].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:2", NULL, NULL);
-    trp[2].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:2", NULL, NULL);
-    trp[2].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:2", NULL, NULL);
-
-    trp[3].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
-    trp[3].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:1", NULL, NULL);
-    trp[3].o = LSUP_term_new(LSUP_TERM_URI, "urn:o:2", NULL, NULL);
-
-    trp[4].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
-    trp[4].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:2", NULL, NULL);
-    trp[4].o = LSUP_term_new(
-            LSUP_TERM_LITERAL, "String 1", NULL, NULL);
-
-    trp[5].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:0", NULL, NULL);
-    trp[5].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:5", NULL, NULL);
-    trp[5].o = LSUP_term_new(
-            LSUP_TERM_LITERAL, "String 2", "xsd:string", NULL);
-
-    trp[6].s = LSUP_term_new(LSUP_TERM_URI, "urn:s:1", NULL, NULL);
-    trp[6].p = LSUP_term_new(LSUP_TERM_URI, "urn:p:6", NULL, NULL);
-    trp[6].o = LSUP_term_new(
-            LSUP_TERM_LITERAL, "String 3", "xsd:string", "es-ES");
-
-    // Let's reuse pointers. Do not double-free.
-    trp[7].s = trp[0].s; // <urn:s:0>
-    trp[7].p = trp[2].p; // <urn:p:2>
-    trp[7].o = trp[5].o; // "String 2"^^xsd:string
-
-    // Duplicate of trp[7]. Do not double-free.
-    trp[8].s = trp[0].s;
-    trp[8].p = trp[2].p;
-    trp[8].o = trp[5].o;
-
-    // Duplicate of trp[7] from different pointers with same value.
-    // Do not double-free.
-    trp[9].s = trp[5].s;
-    trp[9].p = trp[4].p;
-    trp[9].o = trp[5].o;
-
-    return 0;
-}
-
-
-static void _free_triples(LSUP_Triple *trp)
-{
-    // Last three triples are second pointers.
-    for(int i=0; i < NUM_TRP - 3; i++) {
-        LSUP_term_free(trp[i].s);
-        LSUP_term_free(trp[i].p);
-        LSUP_term_free(trp[i].o);
-    }
-
-    free(trp);
-}
-
+#include "assets.h"
 
 static int test_graph_heap()
 {
@@ -86,8 +17,7 @@ static int test_graph_heap()
 
 static int test_graph_add()
 {
-    LSUP_Triple *trp = malloc(NUM_TRP * sizeof(LSUP_Triple));
-    _create_triples(trp);
+    LSUP_Triple *trp = create_triples();
 
     LSUP_Graph *gr = LSUP_graph_new(NUM_TRP + 2, "urn:gr:2", LSUP_STORE_MEM);
 
@@ -99,7 +29,7 @@ static int test_graph_add()
         printf("OK.\n");
     }
 
-    _free_triples(trp); // gr copied data.
+    free_triples(trp); // gr copied data.
 
     EXPECT_INT_EQ(LSUP_graph_size(gr), 8);
 

+ 47 - 0
test/test_store_mdb.c

@@ -0,0 +1,47 @@
+#include "test.h"
+#include "store_mdb.h"
+#include "assets.h"
+
+static int test_triple_store()
+{
+    char *path = "/tmp/testdb";
+    EXPECT_PASS(LSUP_store_setup(&path));
+
+    LSUP_MDBStore *store;
+    store = LSUP_store_init(path, NULL); // triple store.
+    ASSERT(store != NULL, "Error initializing store!");
+
+    LSUP_Triple *trp = create_triples();
+    LSUP_SerTerm sterms[NUM_TRP][3];
+    LSUP_SerTriple ser_trp[NUM_TRP];
+
+    for (int i = 0; i < NUM_TRP; i++) {
+        ser_trp[i].s = sterms[i];
+        ser_trp[i].p = sterms[i] + 1;
+        ser_trp[i].o = sterms[i] + 2;
+        for (int j = 0; j < 3; j++) {
+            LSUP_term_serialize(
+                    LSUP_triple_term_by_pos(trp + i, j),
+                    LSUP_ser_triple_term_by_pos(ser_trp + i, j));
+        }
+    }
+
+    EXPECT_PASS(LSUP_store_add(store, NULL, ser_trp, NUM_TRP));
+
+    LSUP_store_done(store);
+    free_triples(trp);
+
+    return 0;
+}
+
+static int test_quad_store()
+{
+    return 0;
+}
+
+int store_mdb_test()
+{
+    RUN(test_triple_store);
+    RUN(test_quad_store);
+    return 0;
+}