Support deleting paths (#1383)

This commit is contained in:
Andi 2023-11-02 14:07:48 +01:00 committed by GitHub
parent fdbc390d53
commit c94201621a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 102 additions and 45 deletions

View File

@ -2588,39 +2588,68 @@ void Delete::DeleteCursor::UpdateDeleteBuffer(Frame &frame, ExecutionContext &co
expression_results.emplace_back(expression->Accept(evaluator));
}
auto vertex_auth_checker = [&context](const VertexAccessor &va) -> bool {
#ifdef MG_ENTERPRISE
return !(license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE));
#else
return true;
#endif
};
auto edge_auth_checker = [&context](const EdgeAccessor &ea) -> bool {
#ifdef MG_ENTERPRISE
return !(
license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE)));
#else
return true;
#endif
};
for (TypedValue &expression_result : expression_results) {
AbortCheck(context);
switch (expression_result.type()) {
case TypedValue::Type::Vertex: {
auto va = expression_result.ValueVertex();
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
if (vertex_auth_checker(va)) {
buffer_.nodes.push_back(va);
} else {
throw QueryRuntimeException("Vertex not deleted due to not having enough permission!");
}
#endif
buffer_.nodes.push_back(va);
break;
}
case TypedValue::Type::Edge: {
auto ea = expression_result.ValueEdge();
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW,
query::AuthQuery::FineGrainedPrivilege::UPDATE))) {
if (edge_auth_checker(ea)) {
buffer_.edges.push_back(ea);
} else {
throw QueryRuntimeException("Edge not deleted due to not having enough permission!");
}
#endif
buffer_.edges.push_back(ea);
break;
}
case TypedValue::Type::Path: {
auto path = expression_result.ValuePath();
#ifdef MG_ENTERPRISE
auto edges_res = std::any_of(path.edges().cbegin(), path.edges().cend(),
[&edge_auth_checker](const auto &ea) { return !edge_auth_checker(ea); });
auto vertices_res = std::any_of(path.vertices().cbegin(), path.vertices().cend(),
[&vertex_auth_checker](const auto &va) { return !vertex_auth_checker(va); });
if (edges_res || vertices_res) {
throw QueryRuntimeException(
"Path not deleted due to not having enough permission on all edges and vertices on the path!");
}
#endif
buffer_.nodes.insert(buffer_.nodes.begin(), path.vertices().begin(), path.vertices().end());
buffer_.edges.insert(buffer_.edges.begin(), path.edges().begin(), path.edges().end());
}
case TypedValue::Type::Null:
break;
// check we're not trying to delete anything except vertices and edges
default:
throw QueryRuntimeException("Only edges and vertices can be deleted.");
throw QueryRuntimeException("Edges, vertices and paths can be deleted.");
}
}
}
@ -2789,15 +2818,13 @@ SetProperties::SetPropertiesCursor::SetPropertiesCursor(const SetProperties &sel
namespace {
template <typename T>
concept AccessorWithProperties =
requires(T value, storage::PropertyId property_id, storage::PropertyValue property_value,
std::map<storage::PropertyId, storage::PropertyValue> properties) {
{
value.ClearProperties()
} -> std::same_as<storage::Result<std::map<storage::PropertyId, storage::PropertyValue>>>;
{ value.SetProperty(property_id, property_value) };
{ value.UpdateProperties(properties) };
};
concept AccessorWithProperties = requires(T value, storage::PropertyId property_id,
storage::PropertyValue property_value,
std::map<storage::PropertyId, storage::PropertyValue> properties) {
{ value.ClearProperties() } -> std::same_as<storage::Result<std::map<storage::PropertyId, storage::PropertyValue>>>;
{value.SetProperty(property_id, property_value)};
{value.UpdateProperties(properties)};
};
/// Helper function that sets the given values on either a Vertex or an Edge.
///
@ -5354,7 +5381,8 @@ class HashJoinCursor : public Cursor {
restore_frame(self_.left_symbols_, *left_op_frame_it_);
left_op_frame_it_++;
// When all left frames with the common value have been joined, move on to pulling and joining the next right frame
// When all left frames with the common value have been joined, move on to pulling and joining the next right
// frame
if (common_value_found_ && left_op_frame_it_ == hashtable_[common_value].end()) {
common_value_found_ = false;
}

View File

@ -110,6 +110,9 @@ class EdgeAccessor final {
} // namespace memgraph::storage
static_assert(std::is_trivially_copyable<memgraph::storage::EdgeAccessor>::value,
"storage::EdgeAccessor must be trivially copyable!");
namespace std {
template <>
struct hash<memgraph::storage::EdgeAccessor> {

View File

@ -127,6 +127,9 @@ class VertexAccessor final {
bool for_deleted_{false};
};
static_assert(std::is_trivially_copyable<memgraph::storage::VertexAccessor>::value,
"storage::VertexAccessor must be trivially copyable!");
struct EdgesVertexAccessorResult {
std::vector<EdgeAccessor> edges;
int64_t expanded_count;

View File

@ -219,3 +219,24 @@ Feature: Delete
Then the result should be:
| n1 |
| () |
Scenario: Detach deleting paths
Given an empty graph
And having executed:
"""
CREATE (x:X), (n1), (n2), (n3)
CREATE (x)-[:R]->(n1)
CREATE (n1)-[:R]->(n2)
CREATE (n2)-[:R]->(n3)
"""
When executing query:
"""
MATCH p = (:X)-->()-->()-->()
DETACH DELETE p
"""
Then the result should be empty
And the side effects should be:
| -nodes | 4 |
| -relationships | 3 |
| -labels | 1 |

View File

@ -209,3 +209,25 @@ Feature: Delete
MATCH (n) DETACH DELETE n SET n.prop = 1 WITH n RETURN n
"""
Then an error should be raised
Scenario: Detach deleting paths
Given an empty graph
And having executed:
"""
CREATE (x:X), (n1), (n2), (n3)
CREATE (x)-[:R]->(n1)
CREATE (n1)-[:R]->(n2)
CREATE (n2)-[:R]->(n3)
"""
When executing query:
"""
MATCH p = (:X)-->()-->()-->()
DETACH DELETE p
"""
Then the result should be empty
And the side effects should be:
| -nodes | 4 |
| -relationships | 3 |
| -labels | 1 |

View File

@ -99,26 +99,6 @@ Feature: DeleteAcceptance
| -relationships | 3 |
| -labels | 1 |
Scenario: Detach deleting paths
Given an empty graph
And having executed:
"""
CREATE (x:X), (n1), (n2), (n3)
CREATE (x)-[:R]->(n1)
CREATE (n1)-[:R]->(n2)
CREATE (n2)-[:R]->(n3)
"""
When executing query:
"""
MATCH p = (:X)-->()-->()-->()
DETACH DELETE p
"""
Then the result should be empty
And the side effects should be:
| -nodes | 4 |
| -relationships | 3 |
| -labels | 1 |
Scenario: Undirected expand followed by delete and count
Given an empty graph
And having executed: