#include "lua_volksdata.h" static VOLK_Graph **allocate_graph (lua_State *L) { VOLK_Graph **gp = lua_newuserdatauv (L, sizeof (*gp), 1); luaL_getmetatable (L, "VOLK.Graph"); lua_setmetatable (L, -2); return gp; } /** @brief Create a new graph. * * @param[in] store The store to use as a back end. if unspecified, an * in-memory hashmap is used. * * @param[in] uri_str String to use as the graph URI. It may be namespace- * prefixed, or fully qualified. If not specified, Volksdata generates a * UUID4 URN. * * @param[in[ is_ns Whether the passed string is a namespace-prefixed URI. * Default is false. If this is true, the `uri_str` parameter MUST be passed. */ static int l_graph_new (lua_State *L) { VOLK_Store *store; if (lua_isnoneornil (L, 1)) store = NULL; else store = *(VOLK_Store **)luaL_checkudata (L, 1, "VOLK.Store"); const char *uri_str = lua_tostring (L, 2); bool is_ns = lua_toboolean (L, 3); if (is_ns && !uri_str) luaL_error (L, "URI must be passed if is_ns is true.", 2); VOLK_Graph **gp = allocate_graph (L); *gp = is_ns ? VOLK_graph_new_ns (store, uri_str) : VOLK_graph_new (store, uri_str); LUA_NLCHECK (*gp, "Error creating graph."); LOG_DEBUG ("New graph URI @%p: %s", *gp, VOLK_graph_uri (*gp)->data); return 1; } static int l_graph_list (lua_State *L) { const VOLK_Store *store = *(VOLK_Store **)luaL_checkudata ( L, 1, "VOLK.Store"); VOLK_TermSet *ts = VOLK_graph_list (store); LUA_NLCHECK (ts, "Error retrieving context list."); return tset_to_udata (L, ts); } /* * Class methods. */ static int l_graph_gc (lua_State *L) { VOLK_Graph **gp = luaL_checkudata(L, 1, "VOLK.Graph"); LOG_DEBUG ("Garbage collecting graph @%p", *gp); VOLK_graph_free (*gp); *gp = NULL; return 0; } static int l_graph_get_uri (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); VOLK_Term **tp = lua_newuserdata (L, sizeof *tp); luaL_getmetatable (L, "VOLK.Term"); lua_setmetatable (L, -2); *tp = VOLK_term_copy (VOLK_graph_uri (gr)); LUA_NLCHECK (*tp, "Error getting graph URI."); return 1; } static int l_graph_to_string (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); lua_pushfstring ( L, "VOLK.Graph @%p <%s>: %d triples", gr, VOLK_graph_uri (gr)->data, VOLK_graph_size (gr)); return 1; } static int l_graph_len (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); lua_pushinteger (L, VOLK_graph_size (gr)); return 1; } static int l_graph_copy (lua_State *L) { const VOLK_Graph *src = check_graph (L, 1); VOLK_Graph *dest = check_graph (L, 2); const VOLK_Term *s, *p, *o; if lua_isnoneornil (L, 3) s = NULL; else s = check_term (L, 3); if lua_isnoneornil (L, 4) p = NULL; else p = check_term (L, 4); if lua_isnoneornil (L, 5) o = NULL; else o = check_term (L, 5); LUA_PCHECK (VOLK_graph_copy_contents ( src, dest, s, p, o), "Error copying graph."); return 1; } static int l_graph_equals (lua_State *L) { const VOLK_Graph *gr1 = check_graph (L, 1); const VOLK_Graph *gr2 = check_graph (L, 2); LOG_DEBUG ("Comparing graphs %p %p", gr1, gr2); int eq_rc = VOLK_graph_equals (gr1, gr2); lua_pushboolean (L, eq_rc); return 1; } static int l_graph_contains (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const VOLK_Triple *spo = check_triple (L, 2); lua_pushboolean (L, VOLK_graph_contains (gr, spo)); return 1; } /// Initialize iterative addition. static int l_graph_add_init (lua_State *L) { VOLK_Graph *gr = check_graph (L, 1); VOLK_GraphIterator **it_p = lua_newuserdata (L, sizeof *it_p); luaL_getmetatable (L, "VOLK.GraphIterator"); lua_setmetatable (L, -2); *it_p = VOLK_graph_add_init (gr); LUA_NLCHECK (*it_p, "Error creating graph iterator."); return 1; } /// Add one triple. static int l_graph_add_iter (lua_State *L) { VOLK_GraphIterator **it_p = luaL_checkudata (L, 1, "VOLK.GraphIterator"); const VOLK_Triple *spo = check_triple (L, 2); LUA_PCHECK (VOLK_graph_add_iter (*it_p, spo), "Error adding triple"); return 0; } /// Finalize iterative addition. static int l_graph_add_done (lua_State *L) { VOLK_GraphIterator **it_p = luaL_checkudata (L, 1, "VOLK.GraphIterator"); VOLK_graph_add_done (*it_p); *it_p = 0; // it still gets garbage collected, this prevents double-free. return 0; } /** @brief Add triples from an indexed table. */ static int l_graph_add (lua_State *L) { VOLK_Graph *gr = check_graph (L, 1); int rc; VOLK_rc volk_rc= VOLK_NOACTION; size_t i = 0, ct = 0; VOLK_GraphIterator *it = VOLK_graph_add_init (gr); LUA_NLCHECK (it, "Error creating iterator."); while ((rc = lua_rawgeti (L, 2, ++i)) != LUA_TNIL) { //LOG_DEBUG ("Triple type: %s", lua_typename (L, rc)); const VOLK_Triple *spo = check_triple (L, -1); LOG_DEBUG ( "Got triple %d: {%s %s %s}\n", i, spo->s->data, spo->p->data, spo->o->data); volk_rc = VOLK_graph_add_iter (it, spo); if (volk_rc < VOLK_OK) break; if (volk_rc == VOLK_OK) ct++; }; VOLK_graph_add_done (it); lua_pushinteger (L, ct); if (UNLIKELY (volk_rc < VOLK_OK)) return luaL_error ( L, "Error adding triple at position %d: %s", i, VOLK_strerror (volk_rc)); else return 1; } static int l_graph_remove (lua_State *L) { VOLK_Graph *gr = check_graph (L, 1); const VOLK_Term *s, *p, *o; if lua_isnoneornil (L, 2) s = NULL; else s = check_term (L, 2); if lua_isnoneornil (L, 3) p = NULL; else p = check_term (L, 3); if lua_isnoneornil (L, 4) o = NULL; else o = check_term (L, 4); size_t ct; VOLK_rc rc = VOLK_graph_remove (gr, s, p, o, &ct); LUA_PCHECK (rc, "Error removing triples from graph."); lua_pushinteger (L, ct); return 1; } static int graph_iter_next (lua_State *L) { VOLK_GraphIterator **it_p = lua_touserdata (L, lua_upvalueindex (1)); VOLK_Triple **spo_p = lua_newuserdatauv (L, sizeof (*spo_p), 1); luaL_getmetatable (L, "VOLK.Triple"); lua_setmetatable (L, -2); *spo_p = NULL; VOLK_rc rc = VOLK_graph_iter_next (*it_p, spo_p); if (rc != VOLK_OK) { VOLK_graph_iter_free (*it_p); *it_p = NULL; if (rc == VOLK_END) { lua_pushnil (L); lua_pushstring (L, "End of lookup results."); return 2; } LUA_PCHECK (rc, "Error retrieving a lookup result."); } return 1; } static int l_graph_lookup (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const VOLK_Term *s, *p, *o; if lua_isnoneornil (L, 2) s = NULL; else s = check_term (L, 2); if lua_isnoneornil (L, 3) p = NULL; else p = check_term (L, 3); if lua_isnoneornil (L, 4) o = NULL; else o = check_term (L, 4); VOLK_GraphIterator **it_p = lua_newuserdata (L, sizeof *it_p); *it_p = NULL; luaL_getmetatable (L, "VOLK.GraphIterator"); lua_setmetatable (L, -2); size_t ct; *it_p = VOLK_graph_lookup (gr, s, p, o, &ct); LUA_NLCHECK (*it_p, "Error creating graph iterator."); LOG_DEBUG ("Found triples: %d", ct); lua_pushcclosure (L, graph_iter_next, 1); return 1; } static int graph_encode_iter_next (lua_State *L) { void *it = lua_touserdata (L, lua_upvalueindex (1)); const VOLK_Codec *codec = lua_touserdata (L, lua_upvalueindex (2)); char *out = NULL; VOLK_rc rc = codec->encode_graph_iter (it, &out); LUA_PCHECK (rc, "Encoding failed"); //LOG_DEBUG ("Serialized fragment: %s", out); if (rc == VOLK_END) { codec->encode_graph_done (it); return 0; } lua_pushstring (L, out); free (out); return 1; } static int l_graph_encode_iter (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const char *codec_str = lua_tostring (L, 2); const VOLK_Codec *codec_p; if (strcmp(codec_str, "nt") == 0) codec_p = &nt_codec; else if (strcmp(codec_str, "ttl") == 0) codec_p = &ttl_codec; else return luaL_error(L, "Invalid encoding format: %s", codec_str); void *it = codec_p->encode_graph_init (gr); LUA_NLCHECK (it, "Error creating codec iterator."); lua_pushlightuserdata (L, it); lua_pushlightuserdata (L, (void *)codec_p); lua_pushcclosure (L, graph_encode_iter_next, 2); return 1; } static int l_graph_get (lua_State *L) { VOLK_Term *gr_uri = check_term (L, 1); VOLK_Store *store = *(VOLK_Store **)luaL_checkudata (L, 2, "VOLK.Store"); VOLK_Graph **gp = allocate_graph (L); size_t ct; *gp = VOLK_graph_get (store, gr_uri, &ct); lua_pushinteger (L, ct); return 2; } /** @brief Free an iterator handle. * * This is only called if the iterator is abandoned before the * iteration cycle is over (either by end of loop or error). * * @note A new iterator should not be started without first garbage-collecting * an incomplete one. That would cause a MDB_BAD_RSLOT error on an LMDB-backed * graph, because it attempts to open a new read transaction within the same * thread while the old iterator is keeping the old one open. This could be * fixed with some good judgment. * * From the LMDB manual: * * > A thread can only use one transaction at a time, plus any child * > transactions. */ static int graph_iter_gc (lua_State *L) { VOLK_GraphIterator **it_p = lua_touserdata (L, 1); if (UNLIKELY (!it_p || !*it_p)) return 0; LOG_DEBUG ("Garbage collecting iterator @%p", it_p); VOLK_graph_iter_free (*it_p); *it_p = NULL; return 0; } /** Returns a LinkMap that can be iterated over with iter(). */ static int l_graph_connections (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); VOLK_Term *t = check_term (L, 2); const VOLK_LinkType type = luaL_checkinteger (L, 3); LOG_DEBUG ("Adding term for connections: @%p", *t); VOLK_LinkMap *lm = VOLK_graph_connections (gr, t, type); LUA_NLCHECK (lm, "Error creating link map."); VOLK_LinkMap **lm_p = lua_newuserdata (L, sizeof *lm_p); *lm_p = lm; luaL_getmetatable (L, "VOLK.LinkMap"); lua_setmetatable (L, -2); LUA_NLCHECK (*lm_p, "Error creating Link map."); return 1; } static int l_graph_term_set (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const VOLK_Term *t1 = check_term (L, 2); const VOLK_TriplePos t1_pos = luaL_checkinteger (L, 3); const VOLK_Term *t2 = check_term (L, 4); const VOLK_TriplePos t2_pos = luaL_checkinteger (L, 5); VOLK_TermSet *ts = VOLK_graph_term_set (gr, t1, t1_pos, t2, t2_pos); LUA_NLCHECK (ts, "Error creating term set from graph."); return tset_to_udata (L, ts); } static int l_graph_unique_terms (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const VOLK_TriplePos pos = luaL_checkinteger (L, 2); VOLK_TermSet *ts = VOLK_graph_unique_terms (gr, pos); LUA_NLCHECK (ts, "Error creating term set from unique terms."); return tset_to_udata (L, ts); } /** @brief Get all o's for given s and p as a table of values. * * @param[in] gr Graph to query from. * * @param[in] s Subject to query. * * @param[in] p Predicate to query. * * @return Table of objects found per sp combination. * * @todo This could be reformatted as a term set generator, but it seems more * efficient this way because it iterates over the results only once. */ static int l_graph_attr (lua_State *L) { const VOLK_Graph *gr = check_graph (L, 1); const VOLK_Term *s = check_term (L, 2); const VOLK_Term *p = check_term (L, 3); VOLK_TermSet *ts = VOLK_graph_term_set (gr, s, TRP_POS_S, p, TRP_POS_P); LUA_NLCHECK (ts, "Error creating term set from attribute lookup."); return tset_to_udata (L, ts); } /* * Library setup. */ static const luaL_Reg graph_lib_fn [] = { {"new", l_graph_new}, {"list", l_graph_list}, {"get", l_graph_get}, {NULL} }; /* static const luaL_Reg graph_getters [] = { {"uri", l_graph_get_uri}, {"namespace", l_graph_get_nsm}, {NULL} }; */ /* static const luaL_Reg graph_setters [] = { {"uri", l_graph_set_uri}, {NULL} }; */ static const luaL_Reg graph_lib_meth [] = { {"__eq", l_graph_equals}, {"__gc", l_graph_gc}, //{"__index", get_attr}, //{"__newindex", set_attr}, {"__tostring", l_graph_to_string}, {"__len", l_graph_len}, {"copy", l_graph_copy}, {"add", l_graph_add}, {"add_init", l_graph_add_init}, {"remove", l_graph_remove}, {"get_uri", l_graph_get_uri}, {"lookup", l_graph_lookup}, {"contains", l_graph_contains}, {"connections", l_graph_connections}, {"term_set", l_graph_term_set}, {"unique_terms", l_graph_unique_terms}, {"attr", l_graph_attr}, {"encode", l_graph_encode_iter}, {NULL} }; static const LEnumConst graph_enums[] = { {NULL, 0} }; static const luaL_Reg graph_iter_meth [] = { {"add_iter", l_graph_add_iter}, {"add_done", l_graph_add_done}, {"__gc", graph_iter_gc}, }; int luaopen_volksdata_graph (lua_State *L) { VOLK_init(); // This is idempotent: no problem calling it multiple times. luaL_newmetatable (L, "VOLK.Graph"); lua_pushvalue (L, -1); lua_setfield (L, -2, "__index"); luaL_setfuncs (L, graph_lib_meth, 0); // Metatables for graph iterator. luaL_newmetatable (L, "VOLK.GraphIterator"); lua_pushvalue (L, -1); lua_setfield (L, -2, "__index"); luaL_setfuncs (L, graph_iter_meth, 0); /* // Getters table. lua_newtable (L); for (int i = 0; graph_getters[i].name != NULL; i++) { lua_pushcfunction (L, graph_getters[i].func); lua_setfield (L, -2, graph_getters[i].name); } // Set getters table as a value for the Graph metatable. lua_setfield (L, -2, "getters"); */ luaL_newlib (L, graph_lib_fn); // Module-level constants. push_int_const (L, graph_enums); return 1; }