#include "store_htable.h" #include "store_mdb.h" #include "graph.h" /* * Data types. */ typedef struct Graph { const LSUP_Env * env; // LSUP environment. LSUP_store_type store_type; // Back end type: in-memory or MDB. LSUP_Term *uri; // Graph "name" (URI) union { LSUP_HTStore * ht_store; LSUP_MDBStore * mdb_store; }; // Back end, defined by store_type. 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. union { // Internal store iterator. LSUP_HTIterator * ht_iter; LSUP_MDBIterator * mdb_iter; }; size_t ct; // Total matches. } GraphIterator; /* * Static prototypes. */ 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); #define ENTRY(a, b) (be) == (LSUP_STORE_##a) || static inline bool check_backend (LSUP_store_type be) { return (BACKEND_TBL false); } #undef ENTRY /* * Graph API. */ Graph * LSUP_graph_new_env ( const LSUP_Env *env, LSUP_Term *uri, const LSUP_store_type store_type) { if (UNLIKELY (!env)) { log_error ("No valid environment passed. Did you call LSUP_init()?"); return NULL; } if (UNLIKELY (!check_backend (store_type))) return NULL; LSUP_Graph *gr; MALLOC_GUARD (gr, NULL); gr->uri = uri; gr->store_type = store_type; gr->env = env; gr->nsm = env->nsm; if (gr->store_type == LSUP_STORE_MEM) { gr->ht_store = LSUP_htstore_new(); if (UNLIKELY (!gr->ht_store)) return NULL; } else if (gr->store_type == LSUP_STORE_MDB) { gr->mdb_store = env->mdb_store; } else { // LSUP_STORE_MDB_TMP gr->mdb_store = env->mdb_store_ramdisk; } log_debug ("Graph created."); return gr; } LSUP_Graph ** LSUP_graph_new_lookup_env ( const LSUP_Env *env, const LSUP_Term *s, const LSUP_Term *p, const LSUP_Term *o) { if (UNLIKELY (!env)) { log_error ("No valid environment passed. Did you call LSUP_init()?"); return NULL; } LSUP_Buffer *ss = LSUP_term_serialize (s); LSUP_Buffer *sp = LSUP_term_serialize (p); LSUP_Buffer *so = LSUP_term_serialize (o); LSUP_Buffer **ctx_a = LSUP_mdbstore_lookup_contexts ( env->mdb_store, ss, sp, so); if (UNLIKELY (!ctx_a)) return NULL; LSUP_buffer_free (ss); LSUP_buffer_free (sp); LSUP_buffer_free (so); // Count for allocation. size_t i; for (i = 0; ctx_a[i] != NULL; i++) {} LSUP_Graph **gr_a = calloc (i + 1, sizeof (*gr_a)); if (UNLIKELY (!gr_a)) return NULL; for (i = 0; ctx_a[i] != NULL; i++) { gr_a[i] = LSUP_graph_new ( LSUP_iriref_new (NULL, NULL), LSUP_STORE_MDB); LSUP_Term *uri = LSUP_term_new_from_buffer (ctx_a[i]); gr_a[i]->uri = uri; LSUP_buffer_free (ctx_a[i]); } free (ctx_a); return gr_a; } LSUP_Graph * LSUP_graph_copy (const Graph *src) { LSUP_Graph *dest = LSUP_graph_new_env ( src->env, LSUP_iriref_new (NULL, NULL), src->store_type); if (UNLIKELY (!dest)) return NULL; LSUP_rc rc = graph_copy_contents (src, dest); if (UNLIKELY (rc != LSUP_OK)) return NULL; return dest; } LSUP_rc LSUP_graph_store ( const LSUP_Graph *src, LSUP_Graph **dest_p, const LSUP_Env *env) { if (!env) env = LSUP_default_env; if (src->store_type == LSUP_STORE_MDB && src->env == env) return LSUP_NOACTION; LSUP_Graph *dest = LSUP_graph_new_env ( env, LSUP_iriref_new (src->uri->data, LSUP_iriref_nsm (src->uri)), LSUP_STORE_MDB); if (UNLIKELY (!dest)) return LSUP_DB_ERR; LSUP_rc rc; rc = graph_copy_contents (src, dest); if (UNLIKELY (rc < 0)) return LSUP_DB_ERR; if (src->store_type == LSUP_STORE_MEM) { rc = LSUP_mdbstore_nsm_store (dest->mdb_store, src->nsm); if (UNLIKELY (rc < 0)) return LSUP_DB_ERR; } *dest_p = dest; return LSUP_OK; } // TODO support boolean ops between any types of graphs. Graph * LSUP_graph_bool_op( const LSUP_bool_op op, const Graph *gr1, const Graph *gr2) { 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; } 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; } 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); return res; } void LSUP_graph_free (LSUP_Graph *gr) { if (LIKELY (gr != NULL)) { LSUP_term_free (gr->uri); if (gr->store_type == LSUP_STORE_MEM) LSUP_htstore_free (gr->ht_store); free (gr); } } LSUP_Term * LSUP_graph_uri (const LSUP_Graph *gr) { return gr->uri; } LSUP_rc LSUP_graph_set_uri (LSUP_Graph *gr, LSUP_Term *uri) { if (!LSUP_IS_IRI (uri)) { log_error ("Term provided is not a IRI."); return LSUP_VALUE_ERR; } LSUP_term_free (gr->uri); gr->uri = LSUP_iriref_new (uri->data, LSUP_iriref_nsm (uri)); return LSUP_OK; } LSUP_NSMap * LSUP_graph_namespace (const Graph *gr) { if (gr->store_type == LSUP_STORE_MEM) return gr->nsm; LSUP_NSMap *nsm; if (LSUP_mdbstore_nsm_get (gr->mdb_store, &nsm) < 0) return NULL; return nsm; } 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) { 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); LSUP_graph_iter_free (it); return ct; } } bool LSUP_graph_equals (const Graph *gr1, const Graph *gr2) { LSUP_Graph *res = LSUP_graph_bool_op (LSUP_BOOL_XOR, gr1, gr2); return (LSUP_graph_size (res) == 0); } GraphIterator * LSUP_graph_add_init (LSUP_Graph *gr) { 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); } it->graph = gr; return it; } LSUP_rc LSUP_graph_add_iter (LSUP_GraphIterator *it, const LSUP_Triple *spo) { LSUP_rc rc; 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); 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)) ) { LSUP_Buffer *ser_dtype = LSUP_term_serialize (term->datatype); LSUP_mdbstore_add_term (it->graph->mdb_store, ser_dtype); LSUP_buffer_free (ser_dtype); } } } LSUP_btriple_free (sspo); return rc; } 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."); } LSUP_rc LSUP_graph_add (Graph *gr, const LSUP_Triple trp[], size_t *inserted) { LSUP_rc rc = LSUP_NOACTION; // Initialize iterator. LSUP_GraphIterator *it = LSUP_graph_add_init (gr); // Serialize and insert RDF triples. for (size_t i = 0; trp[i].s != NULL; i++) { log_trace ("Inserting triple #%lu", i); LSUP_rc db_rc = LSUP_graph_add_iter (it, trp + i); if (db_rc == LSUP_OK) rc = LSUP_OK; if (UNLIKELY (db_rc < 0)) return db_rc; } if (inserted) { if (gr->store_type == LSUP_STORE_MEM) { *inserted = LSUP_htiter_cur (it->ht_iter); } else { *inserted = LSUP_mdbiter_cur (it->mdb_iter); } } LSUP_graph_add_done (it); return rc; } LSUP_rc LSUP_graph_remove ( LSUP_Graph *gr, const LSUP_Term *s, const LSUP_Term *p, const LSUP_Term *o, size_t *ct) { LSUP_rc rc; 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 (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); LSUP_buffer_free (ss); LSUP_buffer_free (sp); LSUP_buffer_free (so); LSUP_buffer_free (sc); return rc; } GraphIterator * LSUP_graph_lookup (const Graph *gr, const LSUP_Term *s, const LSUP_Term *p, const LSUP_Term *o, size_t *ct) { GraphIterator *it; MALLOC_GUARD (it, NULL); it->graph = gr; 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); if (ct) *ct = it->ct; } else it->mdb_iter = LSUP_mdbstore_lookup ( it->graph->mdb_store, ss, sp, so, sc, ct); LSUP_buffer_free (ss); LSUP_buffer_free (sp); LSUP_buffer_free (so); LSUP_buffer_free (sc); return it; } LSUP_rc LSUP_graph_iter_next (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. 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); LSUP_rc rc = graph_iter_next_buffer (it, sspo); if (rc == LSUP_OK) { spo->s = LSUP_term_new_from_buffer (sspo->s); if (!spo->s) return LSUP_ERROR; spo->p = LSUP_term_new_from_buffer (sspo->p); if (!spo->p) return LSUP_ERROR; spo->o = LSUP_term_new_from_buffer (sspo->o); if (!spo->o) return LSUP_ERROR; } if (it->graph->store_type == LSUP_STORE_MEM) free (sspo); else LSUP_btriple_free_shallow (sspo); return rc; } void LSUP_graph_iter_free (GraphIterator *it) { if (it->graph->store_type == LSUP_STORE_MEM) LSUP_htiter_free (it->ht_iter); else LSUP_mdbiter_free (it->mdb_iter); free (it); } size_t LSUP_graph_iter_cur (GraphIterator *it) { return LSUP_mdbiter_cur (it->mdb_iter); } bool LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo) { 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; LSUP_triple_free (tmp_spo); LSUP_graph_iter_free (it); return rc; } /* * Static functions. */ /** @brief Advance iterator and return 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; } /** * Extern inline definitions. */ size_t LSUP_graph_size (const LSUP_Graph *gr);