Add path pop to mgp API (#1249)

This commit is contained in:
imilinovic 2023-09-19 12:37:55 +02:00 committed by GitHub
parent b719f0744f
commit 404cdf05d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 96 additions and 0 deletions

View File

@ -477,6 +477,8 @@ inline void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
inline void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
inline void path_pop(mgp_path *path) { MgInvokeVoid(mgp_path_pop, path); }
inline size_t path_size(mgp_path *path) { return MgInvoke<size_t>(mgp_path_size, path); }
inline mgp_vertex *path_vertex_at(mgp_path *path, size_t index) {

View File

@ -333,6 +333,13 @@ class Path:
self._vertices.append(edge.end_id)
self._edges.append((edge.start_id, edge.end_id, edge.id))
def pop(self):
if not self._edges:
raise IndexError("Path contains no relationships.")
self._vertices.pop()
self._edges.pop()
def vertex_at(self, index: int) -> Vertex:
return Vertex(self._vertices[index], self._graph)

View File

@ -543,6 +543,10 @@ void mgp_path_destroy(struct mgp_path *path);
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for path extension.
enum mgp_error mgp_path_expand(struct mgp_path *path, struct mgp_edge *edge);
/// Remove the last node and the last relationship from the path.
/// Return mgp_error::MGP_ERROR_OUT_OF_RANGE if the path contains no relationships.
enum mgp_error mgp_path_pop(struct mgp_path *path);
/// Get the number of edges in a mgp_path.
/// Current implementation always returns without errors.
enum mgp_error mgp_path_size(struct mgp_path *path, size_t *result);

View File

@ -886,6 +886,8 @@ class Path {
/// @brief Adds a relationship continuing from the last node on the path.
void Expand(const Relationship &relationship);
/// @brief Removes the last node and the last relationship from the path.
void Pop();
/// @exception std::runtime_error Path contains element(s) with unknown value.
bool operator==(const Path &other) const;
@ -2995,6 +2997,8 @@ inline Relationship Path::GetRelationshipAt(size_t index) const {
inline void Path::Expand(const Relationship &relationship) { mgp::path_expand(ptr_, relationship.ptr_); }
inline void Path::Pop() { mgp::path_pop(ptr_); }
inline bool Path::operator==(const Path &other) const { return util::PathsEqual(ptr_, other.ptr_); }
inline bool Path::operator!=(const Path &other) const { return !(*this == other); }

View File

@ -983,6 +983,24 @@ class Path:
self._vertices = None
self._edges = None
def pop(self):
"""
Remove the last node and the last relationship from the path.
Raises:
InvalidContextError: If using an invalid `Path` instance
OutOfRangeError: If the path contains no relationships.
Examples:
```path.pop()```
"""
if not self.is_valid():
raise InvalidContextError()
self._path.pop()
# Invalidate our cached tuples
self._vertices = None
self._edges = None
@property
def vertices(self) -> typing.Tuple[Vertex, ...]:
"""
@ -1023,6 +1041,10 @@ class Path:
self._edges = tuple(Edge(self._path.edge_at(i)) for i in range(num_edges))
return self._edges
@property
def length(self) -> int:
return self._path.size()
class Record:
"""Represents a record of resulting field values."""

View File

@ -929,6 +929,25 @@ class Path:
self._vertices = None
self._edges = None
def pop(self):
"""
Remove the last node and the last relationship from the path.
Raises:
InvalidContextError: If using an invalid `Path` instance
OutOfRangeError: If the path contains no relationships.
Examples:
```path.pop()```
"""
if not self.is_valid():
raise InvalidContextError()
self._path.pop()
# Invalidate cached tuples
self._vertices = None
self._edges = None
@property
def vertices(self) -> typing.Tuple[Vertex, ...]:
"""

View File

@ -1180,6 +1180,17 @@ mgp_error mgp_path_expand(mgp_path *path, mgp_edge *edge) {
});
}
mgp_error mgp_path_pop(struct mgp_path *path) {
return WrapExceptions([path] {
if (path->edges.empty()) {
throw std::out_of_range("Path contains no relationships.");
}
path->vertices.pop_back();
path->edges.pop_back();
});
}
namespace {
size_t MgpPathSize(const mgp_path &path) noexcept { return path.edges.size(); }
} // namespace

View File

@ -2204,6 +2204,17 @@ PyObject *PyPathExpand(PyPath *self, PyObject *edge) {
Py_RETURN_NONE;
}
PyObject *PyPathPop(PyPath *self) {
MG_ASSERT(self->path);
MG_ASSERT(self->py_graph);
MG_ASSERT(self->py_graph->graph);
if (RaiseExceptionFromErrorCode(mgp_path_pop(self->path))) {
return nullptr;
}
Py_RETURN_NONE;
}
PyObject *PyPathSize(PyPath *self, PyObject *Py_UNUSED(ignored)) {
MG_ASSERT(self->path);
MG_ASSERT(self->py_graph);
@ -2251,6 +2262,8 @@ static PyMethodDef PyPathMethods[] = {
"Create a path with a starting vertex."},
{"expand", reinterpret_cast<PyCFunction>(PyPathExpand), METH_O,
"Append an edge continuing from the last vertex on the path."},
{"pop", reinterpret_cast<PyCFunction>(PyPathPop), METH_NOARGS,
"Remove the last node and the last relationship from the path."},
{"size", reinterpret_cast<PyCFunction>(PyPathSize), METH_NOARGS, "Return the number of edges in a mgp_path."},
{"vertex_at", reinterpret_cast<PyCFunction>(PyPathVertexAt), METH_VARARGS,
"Return the vertex from a path at given index."},

View File

@ -33,6 +33,16 @@ def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
(2, 1),
)
path.pop()
mock_path.pop()
results["pop"] = test_utils.all_equal(
(len(path.vertices), len(path.edges)),
(len(mock_path.vertices), len(mock_path.edges)),
(1, 0),
)
path.expand(edge_to_add)
mock_path.expand(mock_edge_to_add)
NEXT_ID = 1
results["vertices"] = test_utils.all_equal(
all(isinstance(vertex, mgp.Vertex) for vertex in path.vertices),

View File

@ -125,6 +125,7 @@ def test_path():
"__copy__": True,
"is_valid": True,
"expand": True,
"pop": True,
"vertices": True,
"edges": True,
}

View File

@ -346,6 +346,9 @@ TYPED_TEST(CppApiTestFixture, TestPath) {
auto value_x = mgp::Value(path);
// Use Value move constructor
auto value_y = mgp::Value(mgp::Path(node_0));
path.Pop();
ASSERT_EQ(path.Length(), 0);
}
TYPED_TEST(CppApiTestFixture, TestDate) {