浏览代码

Merge branch 'ctx_list' of scossu/lsup_rdf into master

scossu 3 天之前
父节点
当前提交
7b742c980b
共有 8 个文件被更改,包括 201 次插入28 次删除
  1. 1 1
      Makefile
  2. 16 0
      include/lsup/graph.h
  3. 18 0
      include/lsup/store_interface.h
  4. 1 1
      src/core.c
  5. 30 0
      src/graph.c
  6. 1 1
      src/store.c
  7. 51 8
      src/store_mdb.c
  8. 83 17
      test/test_graph.c

+ 1 - 1
Makefile

@@ -29,7 +29,7 @@ DBG_CFLAGS = $(_CFLAGS) -Itest -O0 -ggdb -DDEBUG
 #$(info DBG_CFLAGS: $(DBG_CFLAGS))
 #$(info DBG_CFLAGS: $(DBG_CFLAGS))
 # NOTE: -luuid is a Linux system library. Other OS's might need a different
 # NOTE: -luuid is a Linux system library. Other OS's might need a different
 # link or a non-system library built.
 # link or a non-system library built.
-LDFLAGS := -L$(libdir) -L$(OUTDIR) -L. -llmdb -lxxhash -luuid
+LDFLAGS := -L$(OUTDIR) -L$(libdir) -L. -llmdb -lxxhash -luuid
 
 
 PARSER = bin/lemon
 PARSER = bin/lemon
 LEMON_SRC = ext/sqlite/tool/lemon.c
 LEMON_SRC = ext/sqlite/tool/lemon.c

+ 16 - 0
include/lsup/graph.h

@@ -466,6 +466,22 @@ LSUP_TermSet *
 LSUP_graph_unique_terms (const LSUP_Graph *gr, LSUP_TriplePos pos);
 LSUP_graph_unique_terms (const LSUP_Graph *gr, LSUP_TriplePos pos);
 
 
 
 
+/** @brief List all graph URIs in a store.
+ *
+ * @param[in] txn Transaction handle to work in. It may be NULL.
+ *
+ * @param[in] store Store to look for graphs.
+ *
+ * @return Term set of graph URIs, or NULL on error.
+ */
+LSUP_TermSet *
+LSUP_graph_list_txn (void *txn, LSUP_Store *store);
+
+
+/// Non-transactional version of #LSUP_graph_list_txn.
+#define LSUP_graph_list(...) LSUP_graph_list_txn (NULL, __VA_ARGS__)
+
+
 /** @brief Add triples for a term and related link map to a graph.
 /** @brief Add triples for a term and related link map to a graph.
  *
  *
  * The link map can be of inbound, outbound, or edge type; depending on
  * The link map can be of inbound, outbound, or edge type; depending on

+ 18 - 0
include/lsup/store_interface.h

@@ -446,6 +446,18 @@ typedef LSUP_rc (*iter_next_fn_t)(
 typedef void (*iter_free_fn_t)(void * it);
 typedef void (*iter_free_fn_t)(void * it);
 
 
 
 
+/** @brief Prototype: Get index of all graph (context) URIs in a store.
+ *
+ * Only applicable to stores with the LSUP_STORE_CTX feature flag.
+ *
+ * @param[in] store Store handle.
+ *
+ * @return Set of all context URIs.
+ */
+typedef LSUP_Buffer ** (*store_ctx_list_fn_t)(void *store, void *txn);
+
+
+
 /*
 /*
  * Iterface type definitions.
  * Iterface type definitions.
  */
  */
@@ -524,6 +536,10 @@ typedef struct store_if_t {
                                         ///< Only available (and mandatory)
                                         ///< Only available (and mandatory)
                                         ///< in stores with the
                                         ///< in stores with the
                                         ///< #LSUP_STORE_IDX feature.
                                         ///< #LSUP_STORE_IDX feature.
+    store_ctx_list_fn_t ctx_list_fn;    ///< Get all context URIs in a store.
+                                        ///<
+                                        ///< Only applicable to stores with
+                                        ///< LSUP_STORE_CTX feature.
 } LSUP_StoreInt;
 } LSUP_StoreInt;
 
 
 
 
@@ -563,6 +579,8 @@ const LSUP_StoreInt my_store_int = {
 
 
     .nsm_put_fn     = my_nsm_put_fn,
     .nsm_put_fn     = my_nsm_put_fn,
     .nsm_get_fn     = my_nsm_get_fn,
     .nsm_get_fn     = my_nsm_get_fn,
+
+    .ctx_list_fn   = my_ctx_list_fn,
 };
 };
 */
 */
 
 

+ 1 - 1
src/core.c

@@ -3,8 +3,8 @@
 #include <ftw.h>
 #include <ftw.h>
 #include <string.h>
 #include <string.h>
 
 
-#include "lsup/core.h"
 #include "lmdb.h"
 #include "lmdb.h"
+#include "lsup/core.h"
 
 
 
 
 bool LSUP_env_is_init = false;
 bool LSUP_env_is_init = false;

+ 30 - 0
src/graph.c

@@ -253,6 +253,9 @@ LSUP_graph_free (LSUP_Graph *gr)
 
 
     LSUP_term_free (gr->uri);
     LSUP_term_free (gr->uri);
     free (gr->store->id);
     free (gr->store->id);
+    // If the store has the nsm_get_fn, it's been uniquely created for this
+    // graph and it's safe to free.
+    if (gr->store->sif->nsm_get_fn) LSUP_nsmap_free (gr->nsm);
     // If the store is a HTable, it means it has been created with the graph
     // If the store is a HTable, it means it has been created with the graph
     // and must go with it.
     // and must go with it.
     if (gr->store->type == LSUP_STORE_HTABLE) {
     if (gr->store->type == LSUP_STORE_HTABLE) {
@@ -637,6 +640,33 @@ LSUP_graph_iter_free (LSUP_GraphIterator *it)
 }
 }
 
 
 
 
+LSUP_TermSet *
+LSUP_graph_list_txn (void *txn, LSUP_Store *store)
+{
+    LSUP_Buffer **tdata = store->sif->ctx_list_fn (store->data, txn);
+    if (UNLIKELY (!tdata)) return NULL;
+
+    LSUP_TermSet *ts = LSUP_term_set_new();
+
+    size_t i = 0;
+    while (tdata[i]) {
+        LSUP_Term *t = LSUP_term_new_from_buffer (tdata[i]);
+        LSUP_rc rc = LSUP_term_set_add (ts, t, NULL);
+        LSUP_buffer_free (tdata[i]);
+        if (UNLIKELY (rc != LSUP_OK)) {
+            LSUP_term_free (t);
+            LSUP_term_set_free (ts);
+
+            return NULL;
+        }
+        i++;
+    }
+    free (tdata);
+
+    return ts;
+}
+
+
 bool
 bool
 LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
 LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo)
 {
 {

+ 1 - 1
src/store.c

@@ -55,7 +55,7 @@ LSUP_store_new (
 void
 void
 LSUP_store_free (LSUP_Store *store)
 LSUP_store_free (LSUP_Store *store)
 {
 {
-    if (!UNLIKELY (!store)) return;
+    if (UNLIKELY (!store)) return;
 
 
     store->sif->free_fn (store->data);
     store->sif->free_fn (store->data);
     if (store->id) free (store->id);
     if (store->id) free (store->id);

+ 51 - 8
src/store_mdb.c

@@ -3,7 +3,7 @@
 /**
 /**
  * Number of DBs defined. See MAIN_TABLE and LOOKUP_TABLE defines below.
  * Number of DBs defined. See MAIN_TABLE and LOOKUP_TABLE defines below.
  */
  */
-#define N_DB 13
+#define N_DB 12
 
 
 /**
 /**
  * Memory map size.
  * Memory map size.
@@ -112,7 +112,6 @@ typedef struct mdbstore_iter_t {
 /*          #ID pfx #DB label   #Flags  */ \
 /*          #ID pfx #DB label   #Flags  */ \
     ENTRY(  T_ST,   "t:st",     0               )   /* Key to ser. term */  \
     ENTRY(  T_ST,   "t:st",     0               )   /* Key to ser. term */  \
     ENTRY(  SPO_C,  "spo:c",    DUPFIXED_MASK   )   /* Triple to context */ \
     ENTRY(  SPO_C,  "spo:c",    DUPFIXED_MASK   )   /* Triple to context */ \
-    ENTRY(  C_,     "c:",       0               )   /* Track empty ctx */   \
     ENTRY(  PFX_NS, "pfx:ns",   0               )   /* Prefix to NS */      \
     ENTRY(  PFX_NS, "pfx:ns",   0               )   /* Prefix to NS */      \
     ENTRY(  IDK_ID, "idk:id",   0               )   /* ID key to ID */      \
     ENTRY(  IDK_ID, "idk:id",   0               )   /* ID key to ID */      \
 
 
@@ -690,7 +689,8 @@ mdbstore_add_abort (void *h)
 
 
 
 
 static LSUP_rc
 static LSUP_rc
-key_to_sterm (MDBIterator *it, const LSUP_Key key, LSUP_Buffer *sterm)
+key_to_sterm (
+        MDBStore *store, MDB_txn *txn, const LSUP_Key key, LSUP_Buffer *sterm)
 {
 {
     LSUP_rc rc = LSUP_NORESULT;
     LSUP_rc rc = LSUP_NORESULT;
     int db_rc;
     int db_rc;
@@ -699,7 +699,7 @@ key_to_sterm (MDBIterator *it, const LSUP_Key key, LSUP_Buffer *sterm)
     key_v.mv_data = (void*)&key;
     key_v.mv_data = (void*)&key;
     key_v.mv_size = KLEN;
     key_v.mv_size = KLEN;
 
 
-    db_rc = mdb_get (it->txn, it->store->dbi[IDX_T_ST], &key_v, &data_v);
+    db_rc = mdb_get (txn, store->dbi[IDX_T_ST], &key_v, &data_v);
 
 
     sterm->flags |= LSUP_BUF_BORROWED;
     sterm->flags |= LSUP_BUF_BORROWED;
     if (db_rc == MDB_SUCCESS) {
     if (db_rc == MDB_SUCCESS) {
@@ -915,9 +915,9 @@ mdbiter_next (
 
 
     if (rc == LSUP_OK) {
     if (rc == LSUP_OK) {
         if (sspo) {
         if (sspo) {
-            key_to_sterm (it, it->spok[0], sspo->s);
-            key_to_sterm (it, it->spok[1], sspo->p);
-            key_to_sterm (it, it->spok[2], sspo->o);
+            key_to_sterm (it->store, it->txn, it->spok[0], sspo->s);
+            key_to_sterm (it->store, it->txn, it->spok[1], sspo->p);
+            key_to_sterm (it->store, it->txn, it->spok[2], sspo->o);
 
 
             // TODO error handling.
             // TODO error handling.
         }
         }
@@ -933,7 +933,7 @@ mdbiter_next (
             if (!ctx) return LSUP_MEM_ERR;
             if (!ctx) return LSUP_MEM_ERR;
 
 
             for (i = 0; it->ck[i]; i++)
             for (i = 0; it->ck[i]; i++)
-                key_to_sterm (it, it->ck[i], ctx + i);
+                key_to_sterm (it->store, it->txn, it->ck[i], ctx + i);
             memset (ctx + i, 0, sizeof (*ctx)); // Sentinel
             memset (ctx + i, 0, sizeof (*ctx)); // Sentinel
 
 
             // TODO error handling.
             // TODO error handling.
@@ -1271,6 +1271,47 @@ fail:
 }
 }
 
 
 
 
+LSUP_Buffer **
+mdbstore_ctx_list (void *h, void *th)
+{
+    MDBStore *store = h;
+    LSUP_rc db_rc;
+    MDB_txn *txn;
+    if (th) txn = th;
+    else CHECK (mdb_txn_begin (store->env, NULL, MDB_RDONLY, &txn), fail);
+
+
+    MDB_cursor *cur;
+    CHECK (mdb_cursor_open (txn, store->dbi[IDX_C_SPO], &cur), fail);
+    MDB_val key, data;
+    db_rc = mdb_cursor_get (cur, &key, &data, MDB_FIRST);
+
+    LSUP_Buffer **tdata = NULL;
+    size_t i = 0;
+    while (db_rc == MDB_SUCCESS) {
+        tdata = realloc (tdata, (i + 1) * sizeof (*tdata));
+        if (UNLIKELY (!tdata)) goto fail;
+        tdata[i] = BUF_DUMMY;
+        LSUP_Key tkey = *(LSUP_Key*)key.mv_data;
+        CHECK (key_to_sterm (store, txn, tkey, tdata[i]), fail);
+        db_rc = mdb_cursor_get (cur, &key, &data, MDB_NEXT_NODUP);
+        i++;
+    }
+
+    tdata = realloc (tdata, i * sizeof (data));
+    tdata[i] = NULL;  // Sentinel
+    mdb_cursor_close (cur);
+    if (txn != th && txn != NULL) mdb_txn_abort (txn);
+
+    return tdata;
+
+fail:
+    if (txn != th && txn != NULL) mdb_txn_abort (txn);
+    if (tdata) free (tdata);
+    return NULL;
+}
+
+
 const LSUP_StoreInt mdbstore_int = {
 const LSUP_StoreInt mdbstore_int = {
     .name           = "MDB Store",
     .name           = "MDB Store",
     .features       = LSUP_STORE_PERM | LSUP_STORE_CTX | LSUP_STORE_IDX
     .features       = LSUP_STORE_PERM | LSUP_STORE_CTX | LSUP_STORE_IDX
@@ -1303,6 +1344,8 @@ const LSUP_StoreInt mdbstore_int = {
 
 
     .nsm_put_fn     = mdbstore_nsm_put,
     .nsm_put_fn     = mdbstore_nsm_put,
     .nsm_get_fn     = mdbstore_nsm_get,
     .nsm_get_fn     = mdbstore_nsm_get,
+
+    .ctx_list_fn   = mdbstore_ctx_list,
 };
 };
 
 
 
 

+ 83 - 17
test/test_graph.c

@@ -11,7 +11,7 @@ _graph_new (LSUP_StoreType type)
     if (sif->setup_fn) sif->setup_fn (NULL, true);
     if (sif->setup_fn) sif->setup_fn (NULL, true);
 
 
     LSUP_Graph *gr;
     LSUP_Graph *gr;
-    LSUP_Store *store;
+    LSUP_Store *store = NULL;
     if (type == LSUP_STORE_HTABLE) {
     if (type == LSUP_STORE_HTABLE) {
         gr = LSUP_graph_new (NULL, NULL, NULL);
         gr = LSUP_graph_new (NULL, NULL, NULL);
     } else {
     } else {
@@ -33,7 +33,7 @@ _graph_new (LSUP_StoreType type)
     EXPECT_INT_EQ (LSUP_graph_size (gr), 0);
     EXPECT_INT_EQ (LSUP_graph_size (gr), 0);
 
 
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -71,7 +71,8 @@ _graph_ns_uri (LSUP_StoreType type)
     LSUP_term_free (comp);
     LSUP_term_free (comp);
 
 
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (store) LSUP_store_free (store);
+    LSUP_nsmap_free (nsm);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -87,7 +88,6 @@ _graph_add (LSUP_StoreType type)
 
 
     LSUP_Graph *gr;
     LSUP_Graph *gr;
     LSUP_Store *store;
     LSUP_Store *store;
-
     if (type == LSUP_STORE_HTABLE) store = NULL;
     if (type == LSUP_STORE_HTABLE) store = NULL;
     else store = LSUP_store_new (type, NULL, 0);
     else store = LSUP_store_new (type, NULL, 0);
 
 
@@ -123,7 +123,7 @@ _graph_add (LSUP_StoreType type)
 
 
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
     LSUP_graph_free (gr2);
     LSUP_graph_free (gr2);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -140,7 +140,9 @@ _graph_get (LSUP_StoreType type)
 
 
     LSUP_Triple **trp = create_triples();
     LSUP_Triple **trp = create_triples();
 
 
-    LSUP_Store *store = LSUP_store_new (type, NULL, 0);
+    LSUP_Store *store;
+    if (type == LSUP_STORE_HTABLE) store = NULL;
+    else store = LSUP_store_new (type, NULL, 0);
     LSUP_Graph
     LSUP_Graph
         *gr1 = LSUP_graph_new (store, NULL, NULL),
         *gr1 = LSUP_graph_new (store, NULL, NULL),
         *gr2 = LSUP_graph_new (store, NULL, NULL);
         *gr2 = LSUP_graph_new (store, NULL, NULL);
@@ -184,7 +186,7 @@ _graph_get (LSUP_StoreType type)
     LSUP_graph_free (gr2);
     LSUP_graph_free (gr2);
     LSUP_graph_free (gr3);
     LSUP_graph_free (gr3);
     LSUP_graph_free (gr4);
     LSUP_graph_free (gr4);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -199,7 +201,9 @@ _graph_link_map (LSUP_StoreType type)
 
 
     LSUP_Triple **trp = create_triples();
     LSUP_Triple **trp = create_triples();
 
 
-    LSUP_Store *store = LSUP_store_new (type, NULL, 0);
+    LSUP_Store *store;
+    if (type == LSUP_STORE_HTABLE) store = NULL;
+    else store = LSUP_store_new (type, NULL, 0);
     LSUP_Graph *gr = LSUP_graph_new (store, NULL, NULL);
     LSUP_Graph *gr = LSUP_graph_new (store, NULL, NULL);
 
 
     size_t ct;
     size_t ct;
@@ -297,7 +301,7 @@ _graph_link_map (LSUP_StoreType type)
 
 
     free_triples (trp);
     free_triples (trp);
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -314,7 +318,9 @@ _graph_bool_ops (LSUP_StoreType type)
 
 
     LSUP_Triple **trp = create_triples();
     LSUP_Triple **trp = create_triples();
 
 
-    LSUP_Store *store = LSUP_store_new (type, NULL, 0);
+    LSUP_Store *store;
+    if (type == LSUP_STORE_HTABLE) store = NULL;
+    else store = LSUP_store_new (type, NULL, 0);
     LSUP_Graph
     LSUP_Graph
         *gr1 = LSUP_graph_new (store, NULL, NULL),
         *gr1 = LSUP_graph_new (store, NULL, NULL),
         *gr2 = LSUP_graph_new (store, NULL, NULL),
         *gr2 = LSUP_graph_new (store, NULL, NULL),
@@ -402,7 +408,7 @@ _graph_bool_ops (LSUP_StoreType type)
     LSUP_graph_free (gr1);
     LSUP_graph_free (gr1);
     LSUP_graph_free (gr2);
     LSUP_graph_free (gr2);
     free_triples (trp);
     free_triples (trp);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -463,7 +469,7 @@ _graph_lookup (LSUP_StoreType type)
     if (sif->setup_fn) sif->setup_fn (NULL, true);
     if (sif->setup_fn) sif->setup_fn (NULL, true);
 
 
     LSUP_Graph *gr;
     LSUP_Graph *gr;
-    LSUP_Store *store;
+    LSUP_Store *store = NULL;
     if (type == LSUP_STORE_HTABLE) {
     if (type == LSUP_STORE_HTABLE) {
         gr = LSUP_graph_new (NULL, NULL, NULL);
         gr = LSUP_graph_new (NULL, NULL, NULL);
     } else {
     } else {
@@ -519,7 +525,7 @@ _graph_lookup (LSUP_StoreType type)
 
 
     free_triples (trp);
     free_triples (trp);
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -534,7 +540,7 @@ _graph_remove (LSUP_StoreType type)
     LSUP_Triple **trp = create_triples();
     LSUP_Triple **trp = create_triples();
 
 
     LSUP_Graph *gr;
     LSUP_Graph *gr;
-    LSUP_Store *store;
+    LSUP_Store *store = NULL;
     if (type == LSUP_STORE_HTABLE) {
     if (type == LSUP_STORE_HTABLE) {
         gr = LSUP_graph_new (NULL, NULL, NULL);
         gr = LSUP_graph_new (NULL, NULL, NULL);
     } else {
     } else {
@@ -565,7 +571,7 @@ _graph_remove (LSUP_StoreType type)
     free_triples (trp); // gr copied data.
     free_triples (trp); // gr copied data.
 
 
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     // TODO Test complete removal of triples from index when they are not
     // TODO Test complete removal of triples from index when they are not
     // in another context.
     // in another context.
@@ -611,7 +617,7 @@ _graph_txn (LSUP_StoreType type)
     EXPECT_INT_EQ (LSUP_graph_size (gr), 8);
     EXPECT_INT_EQ (LSUP_graph_size (gr), 8);
 
 
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
 
 
     free_triples (trp); // gr copied data.
     free_triples (trp); // gr copied data.
 
 
@@ -672,7 +678,52 @@ _graph_relative (LSUP_StoreType type)
     LSUP_term_free (rel_o);
     LSUP_term_free (rel_o);
     free (rel_spo);
     free (rel_spo);
     LSUP_graph_free (gr);
     LSUP_graph_free (gr);
-    if (type != LSUP_STORE_HTABLE) LSUP_store_free (store);
+    LSUP_store_free (store);
+
+    return 0;
+}
+
+
+static int
+_graph_list (LSUP_StoreType type)
+{
+    const LSUP_StoreInt *sif = LSUP_store_int (type);
+    if (!(sif->features & LSUP_STORE_CTX)) return 0;
+
+    if (sif->setup_fn) sif->setup_fn (NULL, true);
+
+    LSUP_Store *store = LSUP_store_new (type, NULL, 0);
+
+    LSUP_Graph *gg[3] = {
+        LSUP_graph_new (store,  "urn:gr:1", NULL),
+        LSUP_graph_new (store,  "urn:gr:2", NULL),
+        LSUP_graph_new (store,  "urn:gr:3", NULL),
+    };
+
+    LSUP_Triple **trp = create_triples();
+    LSUP_graph_add (gg[0], trp, NULL);  // Add some triples to the 1st graph.
+    LSUP_graph_add (gg[1], trp + 4, NULL);  // Same with the 2nd graph.
+    free_triples (trp);
+
+    LSUP_TermSet *ts = LSUP_graph_list (store);
+    ASSERT (ts != NULL, "Error creating context list!");
+
+    EXPECT_INT_EQ (hashmap_count (ts), 2);
+
+    // Check if first 2 graphs (with triples) are in the context set.
+    LSUP_Hash key;
+    key = LSUP_term_hash (LSUP_graph_uri (gg[0]));
+    ASSERT (LSUP_term_set_get (ts, key) != NULL, "Context  #1 not found!");
+    key = LSUP_term_hash (LSUP_graph_uri (gg[1]));
+    ASSERT (LSUP_term_set_get (ts, key) != NULL, "Context #2 not found!");
+    key = LSUP_term_hash (LSUP_graph_uri (gg[2]));
+    ASSERT (
+            LSUP_term_set_get (ts, key) == NULL,
+            "Empty context shoud not be here!");
+
+    for (size_t i = 0; i < 3; i++) LSUP_graph_free (gg[i]);
+    LSUP_term_set_free (ts);
+    LSUP_store_free (store);
 
 
     return 0;
     return 0;
 }
 }
@@ -809,6 +860,20 @@ BACKEND_TBL
 }
 }
 
 
 
 
+static int test_graph_list()
+{
+    /*
+     * Test relative URIs in graphs.
+     */
+#define ENTRY(a, b) \
+    if (_graph_list (LSUP_STORE_##a) != 0) return -1;
+BACKEND_TBL
+#undef ENTRY
+
+    return 0;
+}
+
+
 static int test_graph_copy()
 static int test_graph_copy()
 {
 {
     LSUP_Triple **trp = create_triples();
     LSUP_Triple **trp = create_triples();
@@ -865,6 +930,7 @@ int graph_tests()
     RUN (test_graph_copy);
     RUN (test_graph_copy);
     RUN (test_graph_txn);
     RUN (test_graph_txn);
     RUN (test_graph_relative);
     RUN (test_graph_relative);
+    RUN (test_graph_list);
 
 
     return 0;
     return 0;
 }
 }