Prechádzať zdrojové kódy

Add filter for graph_copy_contents.

scossu 1 týždeň pred
rodič
commit
347ef0cf08
9 zmenil súbory, kde vykonal 158 pridanie a 49 odobranie
  1. 101 19
      cpython/py_graph.h
  2. 1 1
      include/core.h
  3. 17 4
      include/graph.h
  4. 3 3
      include/store_interface.h
  5. 1 1
      pyproject.toml
  6. 2 3
      setup.py
  7. 5 5
      src/graph.c
  8. 24 11
      test/cpython_test.py
  9. 4 2
      test/test_graph.c

+ 101 - 19
cpython/py_graph.h

@@ -198,7 +198,7 @@ Graph_dealloc (GraphObject *self)
 static PyObject *
 Graph_get_uri (GraphObject *self, void *closure)
 {
-    LSUP_Term *uri = LSUP_graph_uri (self->ob_struct);
+    const LSUP_Term *uri = LSUP_graph_uri (self->ob_struct);
     LOG_DEBUG("Graph URI address: %p", uri);
     LOG_DEBUG("Graph URI: %s", uri->data);
 
@@ -231,19 +231,6 @@ static PyGetSetDef Graph_getsetters[] = {
 };
 
 
-static int
-Graph_copy_contents (GraphObject *self, GraphObject *dest)
-{
-    if (LSUP_graph_copy_contents (self->ob_struct, dest->ob_struct) < LSUP_OK)
-    {
-        PyErr_SetString (PyExc_ValueError, "Error copying graph contents.");
-        return -1;
-    }
-
-    return 0;
-};
-
-
 static PyObject *
 Graph_new_from_rdf (PyTypeObject *cls, PyObject *args)
 {
@@ -335,6 +322,76 @@ inline static int build_trp_pattern (PyObject *args, LSUP_Term *spo[])
     return 0;
 }
 
+/** @brief Converter for a term struct in `O&` notation.
+ *
+ * See https://docs.python.org/3/c-api/arg.html#other-objects
+ * Convert a PyObject argument into a LSUP_term. `Py_None` is acceptable.
+ *
+ * @param[in] obj Argument passed to calling function.
+ *
+ * @param[out] result Result of transform process.
+ *
+ * @return 1 on conversion success; 0 in failure.
+ */
+static int arg_term_converter (PyObject *obj, void *result)
+{
+    if (obj != Py_None && !PyObject_TypeCheck (obj, &TermType)) {
+        PyErr_SetString (PyExc_TypeError, "Variable must be a Term or None.");
+        return 0;
+    }
+
+    LSUP_Term **term = result;
+    *term = obj != Py_None ? ((TermObject *)obj)->ob_struct : NULL;
+
+    return 1;
+}
+
+
+/** @brief Converter for a graph struct in `O&` notation.
+ *
+ * See https://docs.python.org/3/c-api/arg.html#other-objects
+ * Convert a PyObject argument into a LSUP_term.
+ *
+ * @param[in] obj Argument passed to calling function.
+ *
+ * @param[out] result Result of transform process.
+ *
+ * @return 1 on conversion success; 0 in failure.
+ */
+static int arg_graph_converter (PyObject *obj, void *result);
+
+
+static PyObject *
+Graph_copy_contents (GraphObject *self, PyObject *args, PyObject *kwargs)
+{
+    assert (!PyErr_Occurred());
+    assert (args || kwargs);
+
+    LSUP_Term *spo[3];
+    LSUP_Graph *dest;
+
+    static char *kwlist[] = {"", "s", "p", "o", NULL};
+    if (!PyArg_ParseTupleAndKeywords(
+        args, kwargs, "O&|O&O&O&", kwlist,
+        arg_graph_converter, &dest,
+        arg_term_converter, spo,
+        arg_term_converter, spo + 1,
+        arg_term_converter, spo + 2
+    )) return NULL;
+
+    if (LSUP_graph_copy_contents (
+                self->ob_struct, dest,
+                spo[0], spo[1], spo[2])
+            < LSUP_OK)
+    {
+        PyErr_SetString (PyExc_ValueError, "Error copying graph contents.");
+
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+};
+
 
 static PyObject *
 Graph_richcmp (PyObject *self, PyObject *other, int op)
@@ -433,8 +490,13 @@ static PyObject *Graph_remove (PyObject *self, PyObject *args)
     LSUP_rc rc;
     LSUP_Term *spo[3];
 
-    rc = build_trp_pattern (args, spo);
-    if (rc < 0) goto finally;
+    if (!PyArg_ParseTuple (
+        args,
+        "O&O&O&",
+        arg_term_converter, spo,
+        arg_term_converter, spo + 1,
+        arg_term_converter, spo + 2
+    )) Py_RETURN_NONE;
 
     size_t ct;
     rc = LSUP_graph_remove (
@@ -460,8 +522,13 @@ static PyObject *Graph_lookup (PyObject *self, PyObject *args)
     GraphIteratorObject *it_obj = NULL;
     LSUP_Term *spo[3];
 
-    rc = build_trp_pattern (args, spo);
-    if (UNLIKELY (rc < 0)) goto finally;
+    if (!PyArg_ParseTuple (
+        args,
+        "O&O&O&",
+        arg_term_converter, spo,
+        arg_term_converter, spo + 1,
+        arg_term_converter, spo + 2
+    )) Py_RETURN_NONE;
 
     size_t ct;
     LSUP_GraphIterator *it = LSUP_graph_lookup (
@@ -521,7 +588,8 @@ Graph_encode (PyObject *self, PyObject *args)
 
 static PyMethodDef Graph_methods[] = {
     {
-        "copy", (PyCFunction) Graph_copy_contents, METH_CLASS | METH_VARARGS,
+        "copy", (PyCFunction) Graph_copy_contents,
+        METH_VARARGS | METH_KEYWORDS,
         "Copy the contents of a graph into another."
     },
     {
@@ -614,4 +682,18 @@ PyTypeObject GraphType = {
     .tp_as_sequence     = &Graph_seq_methods,
 };
 
+
+static int arg_graph_converter (PyObject *obj, void *result)
+{
+    if (!PyObject_TypeCheck (obj, &GraphType)) {
+        PyErr_SetString (PyExc_TypeError, "Variable must be a Graph object.");
+        return 0;
+    }
+
+    LSUP_Graph **gr = result;
+    *gr = ((GraphObject *)obj)->ob_struct;
+
+    return 1;
+}
+
 #endif

+ 1 - 1
include/core.h

@@ -144,7 +144,7 @@ typedef int LSUP_rc;
  */
 #define LSUP_CONFLICT_ERR   -88891
 
-/// Error while handling environment setup or teardown.
+/// Error while handling environment setup.
 #define LSUP_ENV_ERR        -88890
 
 /*

+ 17 - 4
include/graph.h

@@ -69,16 +69,26 @@ LSUP_graph_get_txn (
  *
  * The destination graph is not initialized here, so the copy is cumulative.
  *
+ * A 3-term pattern may be provided to filter triples to be extracted from the
+ * source graph. If all terms are NULL, all triples are copied.
+ *
  * @param[in] txn Transaction handle. It may be NULL, or an open transaction
  *  handle, in which case the copy is done within the specified transaction.
  *
  * @param src[in] Source graph.
  *
  * @param dest[in] Destination graph.
+ *
+ * @param[in] s, p, o Terms to look up for filtering. Any and all terms can be
+ * NULL, which indicate unbound terms.
+ *
+ * @return LSUP_OK on success; LSUP_NOACTION if no triples were copied; <0
+ *  if an error occurred.
  */
 LSUP_rc
 LSUP_graph_copy_contents_txn (
-        void *txn, const LSUP_Graph *src, LSUP_Graph *dest);
+        void *txn, const LSUP_Graph *src, LSUP_Graph *dest,
+        const LSUP_Term *s, const LSUP_Term *p, const LSUP_Term *o);
 
 
 /// Non-transactional version of #LSUP_graph_copy_contents_txn.
@@ -294,12 +304,15 @@ LSUP_graph_remove_txn (
  *
  * @param[in] txn Transaction handle. It may be NULL.
  *
- * @param gr[in] Graph to look up.
+ * @param[in] gr Graph to look up.
  *
- * @param spo[in] Triple to look for. Any and all terms can be NULL, which
+ * @param[in] s, p, o Terms to look for. Any and all terms can be NULL, which
  *  indicate unbound terms.
  *
- * @param it[out] Pointer to a #LSUP_GraphIterator to be generated. It must be
+ * @param[out] ct If not NULL, this handle is populated with the number of
+ *  entries found.
+ *
+ * @return Pointer to a #LSUP_GraphIterator to be generated. It must be
  *  freed with #LSUP_graph_iter_free after use.
  */
 LSUP_GraphIterator *

+ 3 - 3
include/store_interface.h

@@ -288,9 +288,9 @@ typedef LSUP_rc (*store_add_term_fn_t)(
  *   how this is interpreted.
  *
  * @param[out] ct If not NULL, this will be populated with the number of
- *  entries found. It is very inexpensive to set for lookups without context,
- *  much less so for 1-bound and 2-bound context lookups, in which cases it
- *  should be set only if needed.
+ *  entries found. In some implementations, it is very inexpensive to set for
+ *  lookups without context, much less so for 1-bound and 2-bound context
+ *  lookups, in which cases it should be set only if needed.
  *
  * @return Iterator handle that will be populated with a result iterator. This
  * is always created even if no matches are found and must be freed with

+ 1 - 1
pyproject.toml

@@ -1,3 +1,3 @@
 [build-system]
-requires = ["setuptools>=60"]
+requires = ["setuptools>=75"]
 build-backend = "setuptools.build_meta"

+ 2 - 3
setup.py

@@ -51,8 +51,7 @@ compile_args = [
 if debug:
     print("Compiling with debug flags.")
     compile_args.extend([
-        "-UNDEBUG", "-U_FORTIFY_SOURCE",
-        "-DDEBUG", "-g3", "-O1"
+        "-UNDEBUG", "-DDEBUG", "-D_FORTIFY_SOURCE=0", "-g3", "-O0"
     ])
 else:
     compile_args.extend(["-g0", "-O3"])
@@ -98,7 +97,7 @@ setup(
         "Intended Audience :: Information Technology",
         "Intended Audience :: Science/Research",
         "Intended Audience :: Telecommunications Industry",
-        "License :: Public Domain",
+        "License :: CC-PDDC",
         "Natural Language :: English",
         "Operating System :: POSIX :: BSD",
         "Operating System :: POSIX :: Linux",

+ 5 - 5
src/graph.c

@@ -118,9 +118,9 @@ LSUP_graph_bool_op_txn (
 
     if (op == LSUP_BOOL_UNION) {
         // No need to use a transaction here: the graph is freed on failure.
-        rc = LSUP_graph_copy_contents (gr1, res);
+        rc = LSUP_graph_copy_contents (gr1, res, NULL, NULL, NULL);
         PCHECK (rc, fail);
-        rc = LSUP_graph_copy_contents (gr2, res);
+        rc = LSUP_graph_copy_contents (gr2, res, NULL, NULL, NULL);
         PCHECK (rc, fail);
 
         return LSUP_OK;
@@ -425,12 +425,12 @@ LSUP_graph_remove_txn (
 
 LSUP_rc
 LSUP_graph_copy_contents_txn (
-        void *txn, const LSUP_Graph *src, LSUP_Graph *dest)
+        void *txn, const LSUP_Graph *src, LSUP_Graph *dest,
+        const LSUP_Term *s, const LSUP_Term *p, const LSUP_Term *o)
 {
     LSUP_rc rc = LSUP_NOACTION;
 
-    LSUP_GraphIterator *it = LSUP_graph_lookup_txn (
-            txn, src, NULL, NULL, NULL, NULL);
+    LSUP_GraphIterator *it = LSUP_graph_lookup_txn (txn, src, s, p, o, NULL);
 
     LSUP_Triple *spo = NULL;
     LSUP_GraphIterator *add_it = LSUP_graph_add_init_txn (txn, dest);

+ 24 - 11
test/cpython_test.py

@@ -20,12 +20,11 @@ class TestTerm(unittest.TestCase):
         self.p3 = IRIRef("urn:p:3")
         self.o3 = IRIRef("urn:o:3")
 
-        self.trp = [
-            triple.Triple(self.s1, self.p1, self.o1),
-            triple.Triple(self.s2, self.p2, self.o2),
-        ]
+        self.t1 = triple.Triple(self.s1, self.p1, self.o1)
+        self.t2 = triple.Triple(self.s2, self.p2, self.o2)
         self.t3 = triple.Triple(self.s3, self.p3, self.o3)
         self.t4 = triple.Triple(self.s1, self.p1, self.o1)
+        self.t5 = triple.Triple(self.s1, self.p2, self.o1)
 
     def test_iriref(self):
         uri = IRIRef("urn:s:1")
@@ -76,30 +75,44 @@ class TestTerm(unittest.TestCase):
         gr = graph.Graph(graph.STORE_HTABLE)
 
         print('Adding triples.')
-        gr.add(self.trp)
+        gr.add([self.t1, self.t2])
 
         self.assertEqual(len(gr), 2)
-        self.assertTrue(self.trp[0] in gr)
-        self.assertTrue(self.trp[1] in gr)
+        self.assertTrue(self.t1 in gr)
+        self.assertTrue(self.t2 in gr)
         self.assertFalse(self.t3 in gr)
         self.assertTrue(self.t4 in gr)
 
         gr.remove(self.s1, None, None)
 
-        self.assertFalse(self.trp[0] in gr)
-        self.assertTrue(self.trp[1] in gr)
+        self.assertFalse(self.t1 in gr)
+        self.assertTrue(self.t2 in gr)
 
         print('Encoded NT:')
         for line in gr.to_rdf('nt'):
             print(line)
 
+    def test_copy(self):
+        gr1 = graph.Graph(graph.STORE_HTABLE)
+        gr1.add([self.t1, self.t2, self.t3, self.t5])
+
+        gr2 = graph.Graph(graph.STORE_HTABLE)
+
+        gr1.copy(gr2, self.s1, None, None)
+
+        self.assertTrue(self.t1 in gr2)
+        self.assertTrue(self.t2 not in gr2)
+        self.assertTrue(self.t3 not in gr2)
+        self.assertTrue(self.t4 in gr2)
+        self.assertTrue(self.t5 in gr2)
+
     def test_deserialize(self):
         print('From file.')
         with open(path.join(TEST_DIR, 'assets', 'test.nt'), 'rb') as fh:
             gr2 = graph.Graph.from_rdf(fh, 'nt')
 
-        self.assertTrue(self.trp[0] in gr2)
-        self.assertTrue(self.trp[1] in gr2)
+        self.assertTrue(self.t1 in gr2)
+        self.assertTrue(self.t2 in gr2)
 
 
 if __name__ == '__main__':

+ 4 - 2
test/test_graph.c

@@ -233,6 +233,8 @@ _graph_bool_ops (LSUP_StoreType type)
 
     // Test union with result graph as one of the sources.
     EXPECT_PASS (LSUP_graph_bool_op (LSUP_BOOL_UNION, gr1, gr2, gr1));
+    for (size_t i = 0; i < 8; i++)
+        ASSERT (LSUP_graph_contains (gr1, trp[i]), "Self-union test failed!");
 
     LSUP_graph_free (gr1);
     LSUP_graph_free (gr2);
@@ -556,7 +558,7 @@ static int test_graph_copy()
 
     // Copy to graph with same store type.
     LSUP_Graph *gr2 = LSUP_graph_new (NULL, NULL, NULL);
-    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr2));
+    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr2, NULL, NULL, NULL));
     EXPECT_INT_EQ (LSUP_graph_size (gr1), LSUP_graph_size (gr2));
 
     for (int i = 0; i < sizeof (trp); i++) {
@@ -568,7 +570,7 @@ static int test_graph_copy()
 
     // Copy to graph with a different store type.
     LSUP_Graph *gr3 = LSUP_graph_new (NULL, NULL, NULL);
-    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr3));
+    EXPECT_PASS (LSUP_graph_copy_contents (gr1, gr3, NULL, NULL, NULL));
     EXPECT_INT_EQ (LSUP_graph_size (gr1), LSUP_graph_size (gr2));
 
     for (int i = 0; i < sizeof (trp); i++) {