Browse Source

Merge branch 'cpython' into master

Stefano Cossu 4 years ago
parent
commit
645c65eda7
15 changed files with 900 additions and 34 deletions
  1. 46 2
      .gitignore
  2. 262 0
      cpython/graph_obj.h
  3. 29 0
      cpython/lsup_rdf/__init__.py
  4. 120 0
      cpython/lsup_rdf_mod.c
  5. 158 0
      cpython/term_obj.h
  6. 123 0
      cpython/triple_obj.h
  7. 12 0
      include/buffer.h
  8. 18 5
      include/graph.h
  9. 9 4
      include/term.h
  10. 2 1
      include/triple.h
  11. 43 0
      setup.py
  12. 33 0
      src/buffer.c
  13. 37 18
      src/graph.c
  14. 4 0
      src/store_mdb.c
  15. 4 4
      src/term.c

+ 46 - 2
.gitignore

@@ -61,6 +61,50 @@ bin/*
 vgcore.*
 callgrind.out.*
 
-# Local IDE
-.vimrc
+
+## PYTHON ##
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+## LOCAL ##
+
 sandbox.c
+
+# IDE
+.syntastic*
+.vimrc
+tags

+ 262 - 0
cpython/graph_obj.h

@@ -0,0 +1,262 @@
+#ifndef _PY_GRAPH_MOD_H
+#define _PY_GRAPH_MOD_H
+
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "graph.h"
+#include "triple_obj.h"
+
+
+typedef struct {
+    PyObject_HEAD
+    LSUP_Graph *ob_struct;
+} GraphObject;
+
+
+static int
+Graph_init (GraphObject *self, PyObject *args, PyObject *kwargs)
+{
+    unsigned char store_type;
+
+    if (!PyArg_ParseTuple (args, "b", &store_type))
+        return -1;
+
+    self->ob_struct = LSUP_graph_new ((LSUP_store_type)store_type);
+    if (!self->ob_struct) {
+        PyErr_SetString (PyExc_ValueError, "Could not create graph.");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+Graph_dealloc (GraphObject *self)
+{
+    LSUP_graph_free (self->ob_struct);
+    Py_TYPE (self)->tp_free ((PyObject *) self);
+}
+
+
+static PyObject *
+Graph_get_size (GraphObject *self, void *closure)
+{
+    size_t gr_size = LSUP_graph_size (self->ob_struct);
+    PyObject *size = PyLong_FromSize_t (gr_size);
+
+    Py_INCREF (size);
+    return size;
+}
+
+/*
+ * FIXME This should be deleted and the setter set to NULL or omitted, but
+ * in doing so Python 3.8 raises a SystemError: bad call flags. See
+ * https://bugs.python.org/issue39884 No resolution is clear yet.
+ */
+static int
+Graph_set_size (GraphObject *self, PyObject value, void *closure)
+{ return -1; }
+
+
+static PyObject *
+Graph_get_uri (GraphObject *self, void *closure)
+{
+    PyObject *uri = PyUnicode_FromString (
+            LSUP_graph_uri (self->ob_struct)->data);
+
+    Py_INCREF (uri);
+    return uri;
+}
+
+
+static int
+Graph_set_uri (GraphObject *self, PyObject *uri, void *closure)
+{
+    if (PyUnicode_READY (uri) != 0) return -1; // TODO redundant?
+    if (PyUnicode_KIND (uri) != PyUnicode_1BYTE_KIND) return -1;
+
+    LSUP_rc rc = LSUP_graph_set_uri (self->ob_struct, PyUnicode_DATA (uri));
+
+    return rc == LSUP_OK ? 0 : -1;
+}
+
+
+static PyGetSetDef Graph_getsetters[] = {
+    {
+        .name = "__len__",
+        .get = (getter) Graph_get_size,
+        .set = (setter) Graph_set_size,
+        .doc = "Number of triples in the graph.",
+        //.closure = NULL,
+    },
+    {
+        "uri", (getter) Graph_get_uri, (setter) Graph_set_uri,
+        "Graph URI.", NULL
+    },
+    {NULL}
+};
+
+
+static PyObject *
+Graph_copy (PyTypeObject *cls, PyObject *src)
+{
+    if (! PyObject_TypeCheck (src, cls)) return NULL;
+
+    GraphObject *res = (GraphObject *) cls->tp_alloc(cls, 0);
+    if (!res) return NULL;
+
+    res->ob_struct = LSUP_graph_copy (((GraphObject *) src)->ob_struct);
+
+    Py_INCREF(res);
+    return (PyObject *) res;
+}
+
+
+/* TODO implement LSUP_graph_equals
+static PyObject *
+Graph_richcmp (PyObject *gr1, PyObject *gr2, int op)
+{
+    if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED;
+
+    PyObject *res = NULL;
+
+    LSUP_Graph *t1 = ((GraphObject *) gr1)->ob_struct;
+    LSUP_Graph *t2 = ((GraphObject *) gr2)->ob_struct;
+
+    if (LSUP_graph_equals (t1, t2) && op == Py_EQ) Py_RETURN_TRUE;
+    Py_RETURN_FALSE;
+ }
+*/
+
+
+/* TODO Implement mdbstore bool ops
+static inline PyObject *
+Graph_bool_op (
+        PyTypeObject *cls, LSUP_bool_op op, PyObject *gr1, PyObject *gr2)
+{
+    if (! PyObject_TypeCheck (gr1, cls) || ! PyObject_TypeCheck (gr2, cls))
+        return NULL;
+
+    GraphObject *res = (GraphObject *) cls->tp_alloc (cls, 0);
+    if (!res) return NULL;
+
+    res->ob_struct = LSUP_graph_bool_op (
+            op, ((GraphObject *) gr1)->ob_struct,
+            ((GraphObject *) gr2)->ob_struct);
+
+    Py_INCREF(res);
+    return (PyObject *) res;
+}
+*/
+
+
+static int Graph_add (PyObject *self, PyObject *triples)
+{
+    // Triple may be any iterable.
+    if (! PyIter_Check (triples)) return -1;
+
+    PyObject *trp_obj = NULL;
+    int rc = 0;
+    size_t i;
+    LSUP_SerTriple *sspo = STRP_DUMMY;
+    LSUP_GraphIterator *it = LSUP_graph_add_stream_init (
+            ((GraphObject *)self)->ob_struct);
+
+    for (i = 0; (trp_obj = PyIter_Next (triples)); i++) {
+        if (!PyObject_TypeCheck (trp_obj, &TripleType)) {
+            rc = -1;
+            goto finalize;
+        }
+
+        TRACE ("Inserting triple #%lu\n", i);
+
+        LSUP_triple_serialize (((TripleObject *)trp_obj)->ob_struct, sspo);
+        LSUP_rc db_rc = LSUP_graph_add_stream_iter (it, sspo);
+
+        if (db_rc == LSUP_OK) rc = LSUP_OK;
+        if (UNLIKELY (db_rc < 0)) {
+            rc = -1;
+            goto finalize;
+        }
+    }
+    LSUP_striple_free (sspo);
+
+finalize:
+    LSUP_graph_add_stream_done (it);
+    LSUP_striple_free (sspo);
+
+    return rc;
+}
+
+
+static int Graph_remove (PyObject *self, PyObject *s, PyObject *p, PyObject *o)
+{
+    return 0;
+}
+
+static PyObject *Graph_lookup (
+        PyObject *self, PyObject *s, PyObject *p, PyObject *o)
+{
+    return NULL;
+}
+
+
+static PyMethodDef Graph_methods[] = {
+    {"copy", (PyCFunction) Graph_copy, METH_CLASS, "Copy a graph."},
+    {"add", (PyCFunction) Graph_add, METH_O, "Add triples to a graph."},
+    {
+        "remove", (PyCFunction) Graph_remove, METH_VARARGS,
+        "Remove triples from a graph by matching a pattern."
+    },
+    {
+        "lookup", (PyCFunction) Graph_lookup, METH_VARARGS,
+        "Look triples in a graph by matching a pattern."
+    },
+};
+
+
+/* TODO Implement mdbstore bool ops
+static inline PyObject *Graph_bool_and (
+        PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
+{ return Graph_bool_op (cls, LSUP_BOOL_INTERSECTION, gr1, gr2); }
+
+static inline PyObject *Graph_bool_or (
+        PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
+{ return Graph_bool_op (cls, LSUP_BOOL_UNION, gr1, gr2); }
+
+static inline PyObject *Graph_bool_subtract (
+        PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
+{ return Graph_bool_op (cls, LSUP_BOOL_SUBTRACTION, gr1, gr2); }
+
+static inline PyObject *Graph_bool_xor (
+        PyTypeObject *cls, PyObject *gr1, PyObject *gr2)
+{ return Graph_bool_op (cls, LSUP_BOOL_XOR, gr1, gr2); }
+
+static PyNumberMethods Graph_number_methods = {
+    .nb_and = (binaryfunc) Graph_bool_and,
+    .nb_or = (binaryfunc) Graph_bool_or,
+    .nb_subtract = (binaryfunc) Graph_bool_subtract,
+    .nb_xor = (binaryfunc) Graph_bool_xor,
+};
+*/
+
+PyTypeObject GraphType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    .tp_name = "graph.Graph",
+    .tp_doc = "RDF graph",
+    .tp_basicsize = sizeof(GraphObject),
+    .tp_itemsize = 0,
+    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_new = PyType_GenericNew,
+    .tp_init = (initproc) Graph_init,
+    .tp_dealloc = (destructor) Graph_dealloc,
+    .tp_methods = Graph_methods,
+    .tp_getset = Graph_getsetters,
+    //.tp_richcompare = Graph_richcmp, TODO implement LSUP_graph_equals
+    //.tp_as_number = &Graph_number_methods,
+};
+
+#endif

+ 29 - 0
cpython/lsup_rdf/__init__.py

@@ -0,0 +1,29 @@
+# lsup_rdf package.
+# All "modules" are inside the monolythic lsup_rdf extension because they have
+# cross-linked symbols, so they need a bit of work to be exposed at the main
+# level.
+
+import importlib.util
+import sys
+
+import _lsup_rdf
+
+
+pkg_path = _lsup_rdf.__file__
+
+
+def _load_module(mod_name, path):
+    """
+    Replacement of deprecated imp.load_dynamic(). See
+    https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
+    """
+    spec = importlib.util.spec_from_file_location(mod_name, path)
+    module = importlib.util.module_from_spec(spec)
+    sys.modules[mod_name] = module
+    spec.loader.exec_module(module)
+
+    return module
+
+
+for mod_name in ('term', 'triple', 'graph'):
+    locals()[mod_name] = _load_module(mod_name, pkg_path)

+ 120 - 0
cpython/lsup_rdf_mod.c

@@ -0,0 +1,120 @@
+/** @file lsup_rdf_mod.c
+ *
+ * @brief LSUP_RDF package.
+ *
+ * This "package" module includes all RDF modules in a monolithic compiled
+ * object, in order to resolve internal symbol dependencies. Individual modules
+ * are extracted and exposed by the package __init__.py .
+ */
+
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+
+#include "term_obj.h"
+#include "triple_obj.h"
+#include "graph_obj.h"
+
+
+static PyModuleDef lsup_rdf_pkg = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "lsup_rdf",
+    .m_doc = "Lakesuperior RDF package.",
+    .m_size = -1,
+};
+
+PyMODINIT_FUNC
+PyInit__lsup_rdf (void)
+{ return PyModule_Create (&lsup_rdf_pkg); }
+
+
+PyModuleDef term_mod = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "term",
+    .m_doc = "RDF term module.",
+    .m_size = -1,
+};
+
+
+PyMODINIT_FUNC
+PyInit_term()
+{
+    if (PyType_Ready (&TermType) < 0) return NULL;
+
+    PyObject *m = PyModule_Create(&term_mod);
+    if (m == NULL) return NULL;
+
+#define ENTRY(a, b) \
+    if (PyModule_AddIntConstant (m, "TERM_" #a, b) < 0) return NULL;
+    TTYPE_TBL
+#undef ENTRY
+
+    Py_INCREF(&TermType);
+    if (PyModule_AddObject(m, "Term", (PyObject *) &TermType) < 0) {
+        Py_DECREF(&TermType);
+        Py_DECREF(m);
+        return NULL;
+    }
+
+    return m;
+}
+
+
+PyModuleDef triple_mod = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "triple",
+    .m_doc = "RDF triple module.",
+    .m_size = -1,
+};
+
+
+PyMODINIT_FUNC
+PyInit_triple()
+{
+    if (PyType_Ready (&TripleType) < 0) return NULL;
+
+    PyObject *m = PyModule_Create(&triple_mod);
+    if (m == NULL) return NULL;
+
+    Py_INCREF(&TripleType);
+    if (PyModule_AddObject(m, "Triple", (PyObject *) &TripleType) < 0) {
+        Py_DECREF(&TripleType);
+        Py_DECREF(m);
+        return NULL;
+    }
+
+    return m;
+}
+
+
+static PyModuleDef graph_mod = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "graph",
+    .m_doc = "RDF graph module.",
+    .m_size = -1,
+};
+
+
+PyMODINIT_FUNC
+PyInit_graph()
+{
+    if (PyType_Ready (&GraphType) < 0) return NULL;
+
+    PyObject *m = PyModule_Create(&graph_mod);
+    if (m == NULL) return NULL;
+
+#define ENTRY(a, b) \
+    if (PyModule_AddIntConstant (m, "STORE_" #a, b) < 0) return NULL;
+    BACKEND_TBL
+#undef ENTRY
+
+    Py_INCREF(&GraphType);
+    if (PyModule_AddObject(m, "Graph", (PyObject *) &GraphType) < 0) {
+        Py_DECREF(&GraphType);
+        Py_DECREF(m);
+        return NULL;
+    }
+
+    return m;
+}
+

+ 158 - 0
cpython/term_obj.h

@@ -0,0 +1,158 @@
+#ifndef _PY_TERM_OBJ_H
+#define _PY_TERM_OBJ_H
+
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "term.h"
+
+
+typedef struct {
+    PyObject_HEAD
+    LSUP_Term * ob_struct;
+} TermObject;
+
+
+static int
+Term_init (TermObject *self, PyObject *args, PyObject *kwargs)
+{
+    unsigned char term_type;
+    char *data = NULL, *datatype = NULL, *lang = NULL;
+
+    static char *kwlist[] = {"", "", "datatype", "lang", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords (
+            args, kwargs, "bs|ss", kwlist,
+            &term_type, &data, &datatype, &lang))
+        return -1;
+
+    self->ob_struct = LSUP_term_new ((LSUP_term_type)term_type, data, datatype, lang);
+    if (!self->ob_struct) {
+        PyErr_SetString (PyExc_ValueError, "Could not create term.");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void
+Term_dealloc (TermObject *self)
+{
+    LSUP_term_free (self->ob_struct);
+    Py_TYPE (self)->tp_free ((PyObject *) self);
+}
+
+
+static PyObject *
+Term_get_type (TermObject *self)
+{
+    PyObject *type = PyLong_FromLong (self->ob_struct->type);
+    Py_INCREF (type);
+
+    return type;
+}
+
+
+static PyObject *
+Term_get_data (TermObject *self)
+{
+    PyObject *data = PyUnicode_FromString (self->ob_struct->data);
+    Py_INCREF (data);
+
+    return data;
+}
+
+
+static PyObject *
+Term_get_datatype (TermObject *self)
+{
+    if (!self->ob_struct->datatype) Py_RETURN_NONE;
+
+    PyObject *datatype = PyUnicode_FromString (self->ob_struct->datatype);
+    Py_INCREF (datatype);
+
+    return datatype;
+}
+
+static PyObject *
+Term_get_lang (TermObject *self)
+{
+    if (
+            !self->ob_struct->datatype || !self->ob_struct->lang ||
+            strlen (self->ob_struct->lang) == 0)
+        Py_RETURN_NONE;
+
+    PyObject *lang = PyUnicode_FromString (self->ob_struct->lang);
+    Py_INCREF (lang);
+
+    return lang;
+}
+
+
+static PyGetSetDef Term_getsetters[] = {
+    {"type", (getter) Term_get_type, NULL, "Term type.", NULL},
+    {"data", (getter) Term_get_data, NULL, "Term data.", NULL},
+    {
+        "datatype", (getter) Term_get_datatype,
+        NULL, "Literal term data type.", NULL
+    },
+    {
+        "lang", (getter) Term_get_lang,
+        NULL, "Literal term language tag.", NULL
+    },
+    {NULL}
+};
+
+
+static PyObject *
+Term_richcmp (PyObject *obj1, PyObject *obj2, int op);
+
+
+PyTypeObject TermType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    .tp_name = "term.Term",
+    .tp_doc = "RDF term",
+    .tp_basicsize = sizeof(TermObject),
+    .tp_itemsize = 0,
+    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_new = PyType_GenericNew,
+    .tp_init = (initproc) Term_init,
+    .tp_dealloc = (destructor) Term_dealloc,
+    .tp_getset = Term_getsetters,
+    .tp_richcompare = Term_richcmp,
+};
+
+
+static PyObject *
+Term_richcmp (PyObject *obj1, PyObject *obj2, int op)
+{
+    PyObject *result = NULL;
+
+    if (
+        ! PyObject_TypeCheck (obj1, &TermType) ||
+        ! PyObject_TypeCheck (obj2, &TermType)
+    ) return NULL;
+
+    int c = 0;
+    LSUP_Term *t1 = ((TermObject *) obj1)->ob_struct;
+    LSUP_Term *t2 = ((TermObject *) obj2)->ob_struct;
+
+    switch (op) {
+        case Py_LT: result = Py_NotImplemented; break;
+        case Py_LE: result = Py_NotImplemented; break;
+        case Py_EQ: c = LSUP_term_equals (t1, t2); break;
+        case Py_NE: c = ! LSUP_term_equals (t1, t2); break;
+        case Py_GT: result = Py_NotImplemented; break;
+        case Py_GE: result = Py_NotImplemented; break;
+    }
+
+    if (!result) result = c ? Py_True : Py_False;
+
+    Py_INCREF(result);
+    return result;
+ }
+
+// END _PY_TERM_OBJ_H
+#endif

+ 123 - 0
cpython/triple_obj.h

@@ -0,0 +1,123 @@
+#ifndef _PY_TRIPLE_OBJ_H
+#define _PY_TRIPLE_OBJ_H
+
+#define PY_SSIZE_T_CLEAN
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "triple.h"
+#include "term_obj.h"
+
+
+typedef struct {
+    PyObject_HEAD
+    LSUP_Triple * ob_struct;
+    TermObject *s;
+    TermObject *p;
+    TermObject *o;
+} TripleObject;
+
+
+static int
+Triple_init (TripleObject *self, PyObject *args)
+{
+    PyObject *s = NULL, *p = NULL, *o = NULL, *tmp;
+
+    if (! PyArg_ParseTuple (
+                args, "O!O!O!", &TermType, &s,
+                &TermType, &p, &TermType, &o))
+        return -1;
+
+    // TODO check for null s, p, o
+    tmp = self->s;
+    Py_INCREF(s);
+    self->s = (TermObject *) s;
+    Py_XDECREF(tmp);
+
+    tmp = self->p;
+    Py_INCREF(p);
+    self->p = (TermObject *) p;
+    Py_XDECREF(tmp);
+
+    tmp = self->o;
+    Py_INCREF(o);
+    self->o = (TermObject *) o;
+    Py_XDECREF(tmp);
+
+    self->ob_struct = LSUP_triple_new (
+            self->s->ob_struct, self->p->ob_struct, self->o->ob_struct);
+    if (!self->ob_struct) {
+        PyErr_SetString (PyExc_ValueError, "Could not create triple.");
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static void
+Triple_dealloc (TripleObject *self)
+{
+    Py_XDECREF(self->s);
+    Py_XDECREF(self->p);
+    Py_XDECREF(self->o);
+    free (self->ob_struct); // Term pointers were allocated independently.
+    Py_TYPE (self)->tp_free ((PyObject *) self);
+}
+
+
+static PyMemberDef Triple_members[] = {
+    {"s", T_OBJECT_EX, offsetof(TripleObject, s), 0, "Triple subject,"},
+    {"p", T_OBJECT_EX, offsetof(TripleObject, p), 0, "Triple predicate."},
+    {"o", T_OBJECT_EX, offsetof(TripleObject, o), 0, "Triple object."},
+    {NULL}
+};
+
+
+static PyObject *
+Triple_richcmp (PyObject *obj1, PyObject *obj2, int op)
+{
+    PyObject *result = NULL;
+
+    int c = 0;
+    LSUP_Triple *t1 = ((TripleObject *) obj1)->ob_struct;
+    LSUP_Triple *t2 = ((TripleObject *) obj2)->ob_struct;
+
+    switch (op) {
+        case Py_LT: result = Py_NotImplemented; break;
+        case Py_LE: result = Py_NotImplemented; break;
+        case Py_EQ: c = (
+                            LSUP_term_equals (t1->s, t2->s) &&
+                            LSUP_term_equals (t1->p, t2->p) &&
+                            LSUP_term_equals (t1->o, t2->o)); break;
+        case Py_NE: c = (!
+                            LSUP_term_equals (t1->s, t2->s) &&
+                            LSUP_term_equals (t1->p, t2->p) &&
+                            LSUP_term_equals (t1->o, t2->o)); break;
+        case Py_GT: result = Py_NotImplemented; break;
+        case Py_GE: result = Py_NotImplemented; break;
+    }
+
+    if (!result) result = c ? Py_True : Py_False;
+
+    Py_INCREF(result);
+    return result;
+ }
+
+
+PyTypeObject TripleType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    .tp_name = "triple.Triple",
+    .tp_doc = "RDF triple",
+    .tp_basicsize = sizeof(TripleObject),
+    .tp_itemsize = 0,
+    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_new = PyType_GenericNew,
+    .tp_init = (initproc) Triple_init,
+    .tp_dealloc = (destructor) Triple_dealloc,
+    .tp_members = Triple_members,
+    .tp_richcompare = Triple_richcmp,
+};
+
+#endif

+ 12 - 0
include/buffer.h

@@ -90,6 +90,18 @@ void LSUP_buffer_free (LSUP_Buffer *buf);
 void LSUP_buffer_print (const LSUP_Buffer *buf);
 
 
+/** @brief Format a buffer into anb ASCII string.
+ *
+ * The string has non-printable characters escaped as "\xNN".
+ *
+ * @param buf[in] Buffer to convert.
+ *
+ * @return Formatted string. It must be freed with free().
+ */
+char *
+LSUP_buffer_as_str (const LSUP_Buffer *buf);
+
+
 /** @brief Compare two buffers.
  *
  * The return value is the same as memcmp.

+ 18 - 5
include/graph.h

@@ -7,13 +7,13 @@
  * Define backend types and checks.
  */
 
-#define BACKEND_TBL                                                         \
-    ENTRY(  LSUP_STORE_MEM         )/* Memory backend, hash map. */         \
-    ENTRY(  LSUP_STORE_MDB         )/* LMDB back end on persistent disk. */ \
-    ENTRY(  LSUP_STORE_MDB_TMP     )/* LMDB back end on RAM disk. */        \
+#define BACKEND_TBL                                              \
+    ENTRY(  MEM,       0)/* Memory backend, hash map. */         \
+    ENTRY(  MDB,       1)/* LMDB back end on persistent disk. */ \
+    ENTRY(  MDB_TMP,   2)/* LMDB back end on RAM disk. */        \
 
 typedef enum LSUP_store_type {
-#define ENTRY(x) x,
+#define ENTRY(a, b) LSUP_STORE_##a = b,
     BACKEND_TBL
 #undef ENTRY
 } LSUP_store_type;
@@ -145,6 +145,19 @@ bool
 LSUP_graph_contains (const LSUP_Graph *gr, const LSUP_Triple *spo);
 
 
+LSUP_GraphIterator *
+LSUP_graph_add_stream_init (LSUP_Graph *gr);
+
+
+LSUP_rc
+LSUP_graph_add_stream_iter (
+        LSUP_GraphIterator *it, const LSUP_SerTriple *sspo);
+
+
+void
+LSUP_graph_add_stream_done (LSUP_GraphIterator *it);
+
+
 /** @brief Add triples and/or serialized triples to a graph.
  *
  * For API users it may be more convenient to use the more specialized

+ 9 - 4
include/term.h

@@ -28,11 +28,16 @@
 typedef XXH64_hash_t LSUP_TermHash64;
 typedef char langtag[LANG_SIZE];
 
+#define TTYPE_TBL \
+    ENTRY (UNDEFINED,     0) \
+    ENTRY (URI,           1) \
+    ENTRY (BNODE,         2) \
+    ENTRY (LITERAL,       3)
+
 typedef enum LSUP_term_type {
-    LSUP_TERM_UNDEFINED,
-    LSUP_TERM_URI,
-    LSUP_TERM_BNODE,
-    LSUP_TERM_LITERAL
+#define ENTRY(a, b) LSUP_TERM_##a = b,
+    TTYPE_TBL
+#undef ENTRY
 } LSUP_term_type;
 
 typedef struct LSUP_Term {

+ 2 - 1
include/triple.h

@@ -44,7 +44,8 @@ LSUP_striple_new_from_triple (const LSUP_Triple *spo);
 
 /** @brief Initialize internal term pointers in a heap-allocated triple.
  *
- * The triple must be freed with #LSUP_triple_free().
+ * NOTE: the term structures are not copied. If the triple is freed with
+ * #LSUP_triple_free(), the originally provided terms are freed too.
  *
  * @param spo[in] Triple pointer to initialize.
  */

+ 43 - 0
setup.py

@@ -0,0 +1,43 @@
+from glob import glob
+from os import path
+from setuptools import Extension, setup
+
+
+ROOT_DIR = path.dirname(path.realpath(__file__))
+MOD_DIR = path.join(ROOT_DIR, 'cpython')
+SRC_DIR = path.join(ROOT_DIR, 'src')
+INCL_DIR = path.join(ROOT_DIR, 'include')
+EXT_DIR = path.join(ROOT_DIR, 'ext')
+
+sources = (
+    glob(path.join(SRC_DIR, '*.c')) +
+    glob(path.join(MOD_DIR, '*.c')) +
+    [
+        path.join(EXT_DIR, 'xxHash', 'xxhash.c'),
+        path.join(EXT_DIR, 'openldap', 'libraries', 'liblmdb', 'mdb.c'),
+        path.join(EXT_DIR, 'openldap', 'libraries', 'liblmdb', 'midl.c'),
+    ]
+)
+
+compile_args = ['-std=c99', '-DDEBUG', '-g3']
+
+
+setup(
+    name="lsup_rdf",
+    version="1.0a1",
+    description='Ultra-compact RDF library.',
+    author='Stefano Cossu <https://notabug.org/scossu>',
+    url='https://notabug.org/scossu/lsup_rdf',
+    license='https://notabug.org/scossu/lsup_rdf/src/master/LICENSE',
+    package_dir={'lsup_rdf': path.join(MOD_DIR, 'lsup_rdf')},
+    packages=['lsup_rdf'],
+    ext_modules=[
+        Extension(
+            "_lsup_rdf",
+            sources,
+            include_dirs=[INCL_DIR],
+            libraries=['uuid'],
+            extra_compile_args=compile_args,
+        ),
+    ],
+)

+ 33 - 0
src/buffer.c

@@ -41,6 +41,39 @@ void LSUP_buffer_print (const LSUP_Buffer *buf)
 }
 
 
+char *
+LSUP_buffer_as_str (const LSUP_Buffer *buf)
+{
+    size_t i, str_size = 1; // terminating NUL
+
+    // Calculate alloc size first.
+    for (i = 0; i < buf->size; i++) {
+        char chr = ((char*)buf->addr)[i];
+        if (isprint (chr)) str_size ++;
+        else str_size += 4; // 4 characters for ASCII representation (\xNN).
+    }
+
+    char *cstr = malloc (str_size);
+
+    size_t cur = 0; // Position in target string.
+    for (i = 0; i < str_size - 1; i++) {
+        char chr = ((char*)buf->addr)[i];
+
+        if (isprint (chr)) {
+            cstr[cur] = chr;
+            cur ++;
+        } else {
+            sprintf (cstr + cur, "\\x%02x", chr);
+            cur += 4;
+        }
+    }
+
+    cstr[str_size - 1] = 0; // terminating NUL.
+
+    return cstr;
+}
+
+
 void LSUP_buffer_done (LSUP_Buffer *buf)
 {
     if (LIKELY (buf)) free (buf->addr);

+ 37 - 18
src/graph.c

@@ -87,7 +87,7 @@ static inline bool is_null_trp (const LSUP_TripleKey *trp)
             *trp[2] == NULL_KEY);
 }
 
-#define ENTRY(x) (be) == (x) ||
+#define ENTRY(a, b) (be) == (LSUP_STORE_##a) ||
 static inline bool
 check_backend (LSUP_store_type be)
 { return (BACKEND_TBL false); }
@@ -252,8 +252,33 @@ LSUP_graph_size (const Graph *gr)
     return LSUP_mdbstore_size (gr->mdb_store);
 }
 
-// TODO Add add_stream_init, add_stream_iter and add_stream_done for streaming
-// functions.
+
+GraphIterator *
+LSUP_graph_add_stream_init (LSUP_Graph *gr)
+{
+    GraphIterator *it = malloc (sizeof (*it));
+    if (!it) return NULL;
+
+    LSUP_Buffer *sc = LSUP_buffer_new_from_term (gr->uri);
+    it->mdb_iter = LSUP_mdbstore_add_init (gr->mdb_store, sc);
+    LSUP_buffer_free (sc);
+
+    it->graph = gr;
+    it->i = 0;
+
+    return it;
+}
+
+
+LSUP_rc
+LSUP_graph_add_stream_iter (LSUP_GraphIterator *it, const LSUP_SerTriple *sspo)
+{ return LSUP_mdbstore_add_iter (it->mdb_iter, sspo); }
+
+
+void
+LSUP_graph_add_stream_done (LSUP_GraphIterator *it)
+{ LSUP_mdbstore_add_done(it->mdb_iter); }
+
 
 LSUP_rc
 LSUP_graph_add(
@@ -318,9 +343,8 @@ LSUP_graph_add(
     } else {
         rc = LSUP_NOACTION;
 
-        LSUP_Buffer *sc = LSUP_buffer_new_from_term (gr->uri);
-        LSUP_MDBIterator *it = LSUP_mdbstore_add_init (gr->mdb_store, sc);
-        LSUP_buffer_free (sc);
+        // Initialize iterator.
+        LSUP_GraphIterator *it = LSUP_graph_add_stream_init (gr);
 
         // Serialize and insert RDF triples.
         if (trp_ct > 0) {
@@ -330,13 +354,9 @@ LSUP_graph_add(
                 TRACE ("Inserting triple #%lu\n", i);
 
                 LSUP_triple_serialize (trp + i, sspo);
-                LSUP_rc db_rc = LSUP_mdbstore_add_iter (it, sspo);
-
-                if (LIKELY (db_rc == LSUP_OK)) {
-                    rc = LSUP_OK;
-                    if (inserted) (*inserted) ++;
-                }
+                LSUP_rc db_rc = LSUP_graph_add_stream_iter (it, sspo);
 
+                if (db_rc == LSUP_OK) rc = LSUP_OK;
                 if (UNLIKELY (db_rc < 0)) return db_rc;
             }
 
@@ -346,16 +366,15 @@ LSUP_graph_add(
         // Insert serialized triples.
         for (size_t i = 0; i < strp_ct; i++) {
             TRACE ("Inserting triple #%lu\n", i);
-            LSUP_rc db_rc = LSUP_mdbstore_add_iter (it, strp + i);
+            LSUP_rc db_rc = LSUP_graph_add_stream_iter (it, strp + i);
 
-            if (LIKELY (db_rc == LSUP_OK)) {
-                rc = LSUP_OK;
-                if (inserted) (*inserted) ++;
-            }
+            if (db_rc == LSUP_OK) rc = LSUP_OK;
             if (UNLIKELY (db_rc < 0)) return db_rc;
         }
 
-        LSUP_mdbstore_add_done (it);
+        LSUP_graph_add_stream_done (it);
+
+        if (inserted) *inserted = it->ct;
 
         return rc;
     }

+ 4 - 0
src/store_mdb.c

@@ -1096,8 +1096,10 @@ lookup_1bound (MDBStore *store, uint8_t idx0, MDBIterator *it, size_t *ct)
             CRITICAL (ct_it = malloc (sizeof (MDBIterator)));
 
             ct_it->luk[0] = it->luk[0];
+            /*
             LSUP_TripleKey ct_spok;
             memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
+            */
             ct_it->ck = it->ck;
             ct_it->store = it->store;
             ct_it->txn = it->txn;
@@ -1207,8 +1209,10 @@ lookup_2bound(
 
             ct_it->luk[0] = it->luk[0];
             ct_it->luk[1] = it->luk[1];
+            /*
             LSUP_TripleKey ct_spok;
             memcpy (ct_it->spok, ct_spok, sizeof (LSUP_TripleKey));
+            */
             ct_it->ck = it->ck;
             ct_it->store = it->store;
             ct_it->txn = it->txn;

+ 4 - 4
src/term.c

@@ -81,7 +81,8 @@ LSUP_term_init(
     // Validate URI.
     if (term->type == LSUP_TERM_URI) {
         if (UNLIKELY (!ptn_init)) {
-            assert (regcomp (&ptn, URI_REGEX_STR, REG_EXTENDED) == 0);
+            int rc = regcomp (&ptn, URI_REGEX_STR, REG_EXTENDED);
+            if (rc != 0) return LSUP_ERROR;
             ptn_init = true;
             atexit (term_cleanup);
         }
@@ -161,12 +162,11 @@ LSUP_term_init(
 LSUP_rc
 LSUP_term_serialize (const LSUP_Term *term, LSUP_Buffer *sterm)
 {
-    size_t size, data_len, datatype_len,
-           data_idx, datatype_idx, lang_idx;
+    size_t size, data_len, datatype_len = 0,
+           data_idx = 1, datatype_idx = 0, lang_idx = 0;
 
     if (UNLIKELY (term == NULL)) return LSUP_NOACTION;
 
-    data_idx = 1;
     data_len = strlen (term->data) + 1;
 
     size = data_idx + data_len;