Other predefined variables (#143)
This commit is contained in:
parent
11c0dde11c
commit
b459639968
@ -436,9 +436,9 @@ void ProcessNodeRow(storage::Storage *store, const std::vector<Field> &fields, c
|
||||
} else {
|
||||
pv_id = storage::PropertyValue(node_id.id);
|
||||
}
|
||||
auto node_property = node.SetProperty(acc.NameToProperty(field.name), pv_id);
|
||||
if (!node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
|
||||
if (!*node_property) throw LoadException("The property '{}' already exists", field.name);
|
||||
auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), pv_id);
|
||||
if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
|
||||
if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name);
|
||||
}
|
||||
id = node_id;
|
||||
} else if (field.type == "LABEL") {
|
||||
@ -448,9 +448,9 @@ void ProcessNodeRow(storage::Storage *store, const std::vector<Field> &fields, c
|
||||
if (!*node_label) throw LoadException("The label '{}' already exists", label);
|
||||
}
|
||||
} else if (field.type != "IGNORE") {
|
||||
auto node_property = node.SetProperty(acc.NameToProperty(field.name), StringToValue(value, field.type));
|
||||
if (!node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
|
||||
if (!*node_property) throw LoadException("The property '{}' already exists", field.name);
|
||||
auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), StringToValue(value, field.type));
|
||||
if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
|
||||
if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name);
|
||||
}
|
||||
}
|
||||
for (const auto &label : additional_labels) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@ -10,6 +11,7 @@
|
||||
#include "query/frontend/semantic/symbol.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
@ -61,15 +63,22 @@ inline void ExpectType(const Symbol &symbol, const TypedValue &value, TypedValue
|
||||
throw QueryRuntimeException("Expected a {} for '{}', but got {}.", expected, symbol.name(), value.type());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept AccessorWithSetProperty = requires(T accessor, const storage::PropertyId key,
|
||||
const storage::PropertyValue new_value) {
|
||||
{ accessor.SetProperty(key, new_value) }
|
||||
->std::same_as<storage::Result<storage::PropertyValue>>;
|
||||
};
|
||||
|
||||
/// Set a property `value` mapped with given `key` on a `record`.
|
||||
///
|
||||
/// @throw QueryRuntimeException if value cannot be set as a property value
|
||||
template <class TRecordAccessor>
|
||||
void PropsSetChecked(TRecordAccessor *record, const storage::PropertyId &key, const TypedValue &value) {
|
||||
template <AccessorWithSetProperty T>
|
||||
storage::PropertyValue PropsSetChecked(T *record, const storage::PropertyId &key, const TypedValue &value) {
|
||||
try {
|
||||
auto maybe_error = record->SetProperty(key, storage::PropertyValue(value));
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto maybe_old_value = record->SetProperty(key, storage::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
switch (maybe_old_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException("Can't serialize due to concurrent operations.");
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@ -81,6 +90,7 @@ void PropsSetChecked(TRecordAccessor *record, const storage::PropertyId &key, co
|
||||
throw QueryRuntimeException("Unexpected error when setting a property.");
|
||||
}
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
} catch (const TypedValueException &) {
|
||||
throw QueryRuntimeException("'{}' cannot be used as a property value.", value.type());
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ class EdgeAccessor final {
|
||||
public:
|
||||
explicit EdgeAccessor(storage::EdgeAccessor impl) : impl_(std::move(impl)) {}
|
||||
|
||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
||||
|
||||
storage::EdgeTypeId EdgeType() const { return impl_.EdgeType(); }
|
||||
|
||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
||||
@ -51,16 +53,16 @@ class EdgeAccessor final {
|
||||
return impl_.GetProperty(key, view);
|
||||
}
|
||||
|
||||
storage::Result<bool> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
|
||||
storage::Result<bool> RemoveProperty(storage::PropertyId key) { return SetProperty(key, storage::PropertyValue()); }
|
||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||
return SetProperty(key, storage::PropertyValue());
|
||||
}
|
||||
|
||||
utils::BasicResult<storage::Error, void> ClearProperties() {
|
||||
auto ret = impl_.ClearProperties();
|
||||
if (ret.HasError()) return ret.GetError();
|
||||
return {};
|
||||
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
||||
return impl_.ClearProperties();
|
||||
}
|
||||
|
||||
VertexAccessor To() const;
|
||||
@ -87,6 +89,8 @@ class VertexAccessor final {
|
||||
public:
|
||||
explicit VertexAccessor(storage::VertexAccessor impl) : impl_(std::move(impl)) {}
|
||||
|
||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
||||
|
||||
auto Labels(storage::View view) const { return impl_.Labels(view); }
|
||||
|
||||
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
|
||||
@ -103,16 +107,16 @@ class VertexAccessor final {
|
||||
return impl_.GetProperty(key, view);
|
||||
}
|
||||
|
||||
storage::Result<bool> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
|
||||
storage::Result<bool> RemoveProperty(storage::PropertyId key) { return SetProperty(key, storage::PropertyValue()); }
|
||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||
return SetProperty(key, storage::PropertyValue());
|
||||
}
|
||||
|
||||
utils::BasicResult<storage::Error, void> ClearProperties() {
|
||||
auto ret = impl_.ClearProperties();
|
||||
if (ret.HasError()) return ret.GetError();
|
||||
return {};
|
||||
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
||||
return impl_.ClearProperties();
|
||||
}
|
||||
|
||||
auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
||||
@ -237,13 +241,45 @@ class DbAccessor final {
|
||||
const storage::EdgeTypeId &edge_type) {
|
||||
auto maybe_edge = accessor_->CreateEdge(&from->impl_, &to->impl_, edge_type);
|
||||
if (maybe_edge.HasError()) return storage::Result<EdgeAccessor>(maybe_edge.GetError());
|
||||
return EdgeAccessor(std::move(*maybe_edge));
|
||||
return EdgeAccessor(*maybe_edge);
|
||||
}
|
||||
|
||||
storage::Result<bool> RemoveEdge(EdgeAccessor *edge) { return accessor_->DeleteEdge(&edge->impl_); }
|
||||
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
||||
auto res = accessor_->DeleteEdge(&edge->impl_);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
storage::Result<bool> DetachRemoveVertex(VertexAccessor *vertex_accessor) {
|
||||
return accessor_->DetachDeleteVertex(&vertex_accessor->impl_);
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<EdgeAccessor>{};
|
||||
}
|
||||
|
||||
return std::make_optional<EdgeAccessor>(*value);
|
||||
}
|
||||
|
||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||
VertexAccessor *vertex_accessor) {
|
||||
using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>;
|
||||
|
||||
auto res = accessor_->DetachDeleteVertex(&vertex_accessor->impl_);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<ReturnType>{};
|
||||
}
|
||||
|
||||
const auto &[vertex, edges] = *value;
|
||||
|
||||
std::vector<EdgeAccessor> deleted_edges;
|
||||
deleted_edges.reserve(edges.size());
|
||||
std::transform(edges.begin(), edges.end(), std::back_inserter(deleted_edges),
|
||||
[](const auto &deleted_edge) { return EdgeAccessor{deleted_edge}; });
|
||||
|
||||
return std::make_optional<ReturnType>(vertex, std::move(deleted_edges));
|
||||
}
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> RemoveVertex(VertexAccessor *vertex_accessor) {
|
||||
|
@ -603,16 +603,23 @@ Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_
|
||||
// auto storage_acc = interpreter_context_->db->Access();
|
||||
// DbAccessor dba(&storage_acc);
|
||||
// auto triggers_acc = interpreter_context_->before_commit_triggers.access();
|
||||
// triggers_acc.insert(Trigger{"BeforeDelete", "UNWIND deletedVertices as u CREATE(:DELETED {id: u.id + 10})",
|
||||
// triggers_acc.insert(Trigger{"BeforeDelete",
|
||||
// "UNWIND deletedVertices as u CREATE(:DELETED_VERTEX {id: id(u) + 10})",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// // triggers_acc.insert(Trigger{"BeforeDelete2", "UNWIND deletedVertices as u SET u.deleted = 0",
|
||||
// // &interpreter_context_->ast_cache, &dba,
|
||||
// // &interpreter_context_->antlr_lock});
|
||||
// // triggers_acc.insert(Trigger{"BeforeDeleteProcedure", "CALL script.procedure(deletedVertices) YIELD * RETURN
|
||||
// // *",
|
||||
// // &interpreter_context_->ast_cache, &dba,
|
||||
// // &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"BeforeCreator", "UNWIND createdVertices as u SET u.before = u.id + 10",
|
||||
// triggers_acc.insert(Trigger{"BeforeDeleteEdge", "UNWIND deletedEdges as u CREATE(:DELETED_EDGE {id: id(u) +
|
||||
// 10})",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// // triggers_acc.insert(Trigger{"BeforeDelete2", "UNWIND deletedEdges as u SET u.deleted = 0",
|
||||
// // &interpreter_context_->ast_cache, &dba,
|
||||
// // &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"BeforeDeleteProcedure", "CALL script.procedure(updatedVertices) YIELD * RETURN *",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"BeforeCreator", "UNWIND createdVertices as u SET u.before = id(u) + 10",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"BeforeCreatorEdge", "UNWIND createdEdges as u SET u.before = id(u) + 10",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"BeforeSetLabelProcedure",
|
||||
// "CALL label.procedure(assignedVertexLabels) YIELD * RETURN *",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// }
|
||||
// {
|
||||
@ -623,10 +630,12 @@ Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"AfterCreator", "UNWIND createdVertices as u SET u.after = u.id + 100",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// triggers_acc.insert(Trigger{"AfterUpdateProcedure", "CALL script.procedure(updatedVertices) YIELD * RETURN *",
|
||||
// &interpreter_context_->ast_cache, &dba, &interpreter_context_->antlr_lock});
|
||||
// }
|
||||
//} catch (const utils::BasicException &e) {
|
||||
// spdlog::critical("Failed to create a trigger because: {}", e.what());
|
||||
//}
|
||||
// } catch (const utils::BasicException &e) {
|
||||
// spdlog::critical("Failed to create a trigger because: {}", e.what());
|
||||
// }
|
||||
}
|
||||
|
||||
PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) {
|
||||
@ -642,6 +651,11 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
|
||||
db_accessor_ = std::make_unique<storage::Storage::Accessor>(interpreter_context_->db->Access());
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
|
||||
if (interpreter_context_->before_commit_triggers.size() > 0 ||
|
||||
interpreter_context_->after_commit_triggers.size() > 0) {
|
||||
trigger_context_.emplace();
|
||||
}
|
||||
};
|
||||
} else if (query_upper == "COMMIT") {
|
||||
handler = [this] {
|
||||
@ -1355,17 +1369,18 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
utils::Downcast<ProfileQuery>(parsed_query.query) || utils::Downcast<DumpQuery>(parsed_query.query))) {
|
||||
db_accessor_ = std::make_unique<storage::Storage::Accessor>(interpreter_context_->db->Access());
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query) &&
|
||||
(interpreter_context_->before_commit_triggers.size() > 0 ||
|
||||
interpreter_context_->after_commit_triggers.size() > 0)) {
|
||||
trigger_context_.emplace();
|
||||
}
|
||||
}
|
||||
|
||||
utils::Timer planning_timer;
|
||||
PreparedQuery prepared_query;
|
||||
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
|
||||
if (interpreter_context_->before_commit_triggers.size() > 0 ||
|
||||
interpreter_context_->after_commit_triggers.size() > 0) {
|
||||
trigger_context_.emplace();
|
||||
}
|
||||
|
||||
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory,
|
||||
trigger_context_ ? &*trigger_context_ : nullptr);
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <cppitertools/imap.hpp>
|
||||
|
||||
#include "query/context.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
@ -23,6 +24,7 @@
|
||||
#include "query/plan/scoped_profile.hpp"
|
||||
#include "query/procedure/mg_procedure_impl.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/csv_parsing.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
@ -208,7 +210,7 @@ bool CreateNode::CreateNodeCursor::Pull(Frame &frame, ExecutionContext &context)
|
||||
if (input_cursor_->Pull(frame, context)) {
|
||||
auto created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterCreatedVertex(created_vertex);
|
||||
context.trigger_context->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -248,8 +250,8 @@ CreateExpand::CreateExpandCursor::CreateExpandCursor(const CreateExpand &self, u
|
||||
|
||||
namespace {
|
||||
|
||||
void CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, VertexAccessor *from, VertexAccessor *to,
|
||||
Frame *frame, ExpressionEvaluator *evaluator) {
|
||||
EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, VertexAccessor *from, VertexAccessor *to,
|
||||
Frame *frame, ExpressionEvaluator *evaluator) {
|
||||
auto maybe_edge = dba->InsertEdge(from, to, edge_info.edge_type);
|
||||
if (maybe_edge.HasValue()) {
|
||||
auto &edge = *maybe_edge;
|
||||
@ -267,6 +269,8 @@ void CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, VertexAccess
|
||||
throw QueryRuntimeException("Unexpected error when creating an edge.");
|
||||
}
|
||||
}
|
||||
|
||||
return *maybe_edge;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -292,19 +296,23 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
|
||||
// create an edge between the two nodes
|
||||
auto *dba = context.db_accessor;
|
||||
switch (self_.edge_info_.direction) {
|
||||
case EdgeAtom::Direction::IN:
|
||||
CreateEdge(self_.edge_info_, dba, &v2, &v1, &frame, &evaluator);
|
||||
break;
|
||||
case EdgeAtom::Direction::OUT:
|
||||
CreateEdge(self_.edge_info_, dba, &v1, &v2, &frame, &evaluator);
|
||||
break;
|
||||
case EdgeAtom::Direction::BOTH:
|
||||
|
||||
auto created_edge = [&] {
|
||||
switch (self_.edge_info_.direction) {
|
||||
case EdgeAtom::Direction::IN:
|
||||
return CreateEdge(self_.edge_info_, dba, &v2, &v1, &frame, &evaluator);
|
||||
case EdgeAtom::Direction::OUT:
|
||||
// in the case of an undirected CreateExpand we choose an arbitrary
|
||||
// direction. this is used in the MERGE clause
|
||||
// it is not allowed in the CREATE clause, and the semantic
|
||||
// checker needs to ensure it doesn't reach this point
|
||||
CreateEdge(self_.edge_info_, dba, &v1, &v2, &frame, &evaluator);
|
||||
case EdgeAtom::Direction::BOTH:
|
||||
return CreateEdge(self_.edge_info_, dba, &v1, &v2, &frame, &evaluator);
|
||||
}
|
||||
}();
|
||||
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterCreatedObject(created_edge);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -320,7 +328,11 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec
|
||||
ExpectType(self_.node_info_.symbol, dest_node_value, TypedValue::Type::Vertex);
|
||||
return dest_node_value.ValueVertex();
|
||||
} else {
|
||||
return CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
auto &created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return created_vertex;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1823,9 +1835,9 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
for (TypedValue &expression_result : expression_results) {
|
||||
if (MustAbort(context)) throw HintedAbortError();
|
||||
if (expression_result.type() == TypedValue::Type::Edge) {
|
||||
auto maybe_error = dba.RemoveEdge(&expression_result.ValueEdge());
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto maybe_value = dba.RemoveEdge(&expression_result.ValueEdge());
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException("Can't serialize due to concurrent operations.");
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@ -1835,6 +1847,10 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when deleting an edge.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context.trigger_context && maybe_value.GetValue()) {
|
||||
context.trigger_context->RegisterDeletedObject(*maybe_value.GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1845,9 +1861,9 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto &va = expression_result.ValueVertex();
|
||||
if (self_.detach_) {
|
||||
auto maybe_error = dba.DetachRemoveVertex(&va);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto res = dba.DetachRemoveVertex(&va);
|
||||
if (res.HasError()) {
|
||||
switch (res.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException("Can't serialize due to concurrent operations.");
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@ -1857,6 +1873,12 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when deleting a node.");
|
||||
}
|
||||
}
|
||||
if (context.trigger_context && res.GetValue()) {
|
||||
context.trigger_context->RegisterDeletedObject(res.GetValue()->first);
|
||||
for (const auto &deleted_edge : res.GetValue()->second) {
|
||||
context.trigger_context->RegisterDeletedObject(deleted_edge);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto res = dba.RemoveVertex(&va);
|
||||
if (res.HasError()) {
|
||||
@ -1873,7 +1895,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
}
|
||||
|
||||
if (context.trigger_context && res.GetValue()) {
|
||||
context.trigger_context->RegisterDeletedVertex(*res.GetValue());
|
||||
context.trigger_context->RegisterDeletedObject(*res.GetValue());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1928,12 +1950,24 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
|
||||
TypedValue rhs = self_.rhs_->Accept(evaluator);
|
||||
|
||||
switch (lhs.type()) {
|
||||
case TypedValue::Type::Vertex:
|
||||
PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
|
||||
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterSetObjectProperty(lhs.ValueVertex(), self_.property_,
|
||||
TypedValue{std::move(old_value)}, std::move(rhs));
|
||||
}
|
||||
break;
|
||||
case TypedValue::Type::Edge:
|
||||
PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
|
||||
}
|
||||
case TypedValue::Type::Edge: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
|
||||
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterSetObjectProperty(lhs.ValueEdge(), self_.property_,
|
||||
TypedValue{std::move(old_value)}, std::move(rhs));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TypedValue::Type::Null:
|
||||
// Skip setting properties on Null (can occur in optional match).
|
||||
break;
|
||||
@ -1973,16 +2007,26 @@ SetProperties::SetPropertiesCursor::SetPropertiesCursor(const SetProperties &sel
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
concept AccessorWithProperties = requires(T value, storage::PropertyId property_id,
|
||||
storage::PropertyValue property_value) {
|
||||
{ value.ClearProperties() }
|
||||
->std::same_as<storage::Result<std::map<storage::PropertyId, storage::PropertyValue>>>;
|
||||
{value.SetProperty(property_id, property_value)};
|
||||
};
|
||||
|
||||
/// Helper function that sets the given values on either a Vertex or an Edge.
|
||||
///
|
||||
/// @tparam TRecordAccessor Either RecordAccessor<Vertex> or
|
||||
/// RecordAccessor<Edge>
|
||||
template <typename TRecordAccessor>
|
||||
void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op) {
|
||||
template <AccessorWithProperties TRecordAccessor>
|
||||
void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op,
|
||||
ExecutionContext *context) {
|
||||
std::optional<std::map<storage::PropertyId, storage::PropertyValue>> old_values;
|
||||
if (op == SetProperties::Op::REPLACE) {
|
||||
auto maybe_error = record->ClearProperties();
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto maybe_value = record->ClearProperties();
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted graph element.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
@ -1994,6 +2038,10 @@ void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const Typed
|
||||
throw QueryRuntimeException("Unexpected error when setting properties.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context->trigger_context) {
|
||||
old_values.emplace(std::move(*maybe_value));
|
||||
}
|
||||
}
|
||||
|
||||
auto get_props = [](const auto &record) {
|
||||
@ -2013,8 +2061,24 @@ void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const Typed
|
||||
return *maybe_props;
|
||||
};
|
||||
|
||||
auto set_props = [record](const auto &properties) {
|
||||
for (const auto &kv : properties) {
|
||||
auto register_set_property = [&](auto returned_old_value, auto key, auto new_value) {
|
||||
auto old_value = [&]() -> storage::PropertyValue {
|
||||
if (!old_values) {
|
||||
return std::move(returned_old_value);
|
||||
}
|
||||
|
||||
if (auto it = old_values->find(key); it != old_values->end()) {
|
||||
return std::move(it->second);
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
context->trigger_context->RegisterSetObjectProperty(*record, key, TypedValue(std::move(old_value)),
|
||||
TypedValue(std::move(new_value)));
|
||||
};
|
||||
|
||||
auto set_props = [&, record](auto properties) {
|
||||
for (auto &kv : properties) {
|
||||
auto maybe_error = record->SetProperty(kv.first, kv.second);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
@ -2029,6 +2093,10 @@ void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const Typed
|
||||
throw QueryRuntimeException("Unexpected error when setting properties.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context->trigger_context) {
|
||||
register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -2040,7 +2108,13 @@ void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const Typed
|
||||
set_props(get_props(rhs.ValueVertex()));
|
||||
break;
|
||||
case TypedValue::Type::Map: {
|
||||
for (const auto &kv : rhs.ValueMap()) PropsSetChecked(record, dba->NameToProperty(kv.first), kv.second);
|
||||
for (const auto &kv : rhs.ValueMap()) {
|
||||
auto key = context->db_accessor->NameToProperty(kv.first);
|
||||
auto old_value = PropsSetChecked(record, key, kv.second);
|
||||
if (context->trigger_context) {
|
||||
register_set_property(std::move(old_value), key, kv.second);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -2048,6 +2122,14 @@ void SetPropertiesOnRecord(DbAccessor *dba, TRecordAccessor *record, const Typed
|
||||
"Right-hand side in SET expression must be a node, an edge or a "
|
||||
"map.");
|
||||
}
|
||||
|
||||
if (context->trigger_context && old_values) {
|
||||
// register removed properties
|
||||
for (auto &[property_id, property_value] : *old_values) {
|
||||
context->trigger_context->RegisterRemovedObjectProperty(*record, property_id,
|
||||
TypedValue(std::move(property_value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -2066,10 +2148,10 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, ExecutionContext &co
|
||||
|
||||
switch (lhs.type()) {
|
||||
case TypedValue::Type::Vertex:
|
||||
SetPropertiesOnRecord(context.db_accessor, &lhs.ValueVertex(), rhs, self_.op_);
|
||||
SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context);
|
||||
break;
|
||||
case TypedValue::Type::Edge:
|
||||
SetPropertiesOnRecord(context.db_accessor, &lhs.ValueEdge(), rhs, self_.op_);
|
||||
SetPropertiesOnRecord(&lhs.ValueEdge(), rhs, self_.op_, &context);
|
||||
break;
|
||||
case TypedValue::Type::Null:
|
||||
// Skip setting properties on Null (can occur in optional match).
|
||||
@ -2127,6 +2209,10 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterSetVertexLabel(vertex, label);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2165,10 +2251,10 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
|
||||
storage::View::NEW);
|
||||
TypedValue lhs = self_.lhs_->expression_->Accept(evaluator);
|
||||
|
||||
auto remove_prop = [property = self_.property_](auto *record) {
|
||||
auto maybe_error = record->RemoveProperty(property);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto remove_prop = [property = self_.property_, &context](auto *record) {
|
||||
auto maybe_old_value = record->RemoveProperty(property);
|
||||
if (maybe_old_value.HasError()) {
|
||||
switch (maybe_old_value.GetError()) {
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove a property on a deleted graph element.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
@ -2182,6 +2268,11 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
|
||||
throw QueryRuntimeException("Unexpected error when removing property.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context.trigger_context) {
|
||||
context.trigger_context->RegisterRemovedObjectProperty(*record, property,
|
||||
TypedValue(std::move(*maybe_old_value)));
|
||||
}
|
||||
};
|
||||
|
||||
switch (lhs.type()) {
|
||||
@ -2234,9 +2325,9 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
|
||||
auto &vertex = vertex_value.ValueVertex();
|
||||
for (auto label : self_.labels_) {
|
||||
auto maybe_error = vertex.RemoveLabel(label);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
auto maybe_value = vertex.RemoveLabel(label);
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException("Can't serialize due to concurrent operations.");
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@ -2247,6 +2338,10 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
throw QueryRuntimeException("Unexpected error when removing labels from a node.");
|
||||
}
|
||||
}
|
||||
|
||||
if (context.trigger_context && *maybe_value) {
|
||||
context.trigger_context->RegisterRemovedVertexLabel(vertex, label);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1,66 +1,297 @@
|
||||
#include "query/trigger.hpp"
|
||||
#include <concepts>
|
||||
|
||||
#include "query/context.hpp"
|
||||
#include "query/cypher_query_interpreter.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/interpret/frame.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
namespace {
|
||||
// clang-format off
|
||||
std::vector<std::pair<Identifier, trigger::IdentifierTag>> GetPredefinedIdentifiers() {
|
||||
return {{{"createdVertices", false}, trigger::IdentifierTag::CREATED_VERTICES},
|
||||
{{"deletedVertices", false}, trigger::IdentifierTag::DELETED_VERTICES}};
|
||||
return {{{"createdVertices", false}, trigger::IdentifierTag::CREATED_VERTICES },
|
||||
{{"createdEdges", false}, trigger::IdentifierTag::CREATED_EDGES },
|
||||
{{"deletedVertices", false}, trigger::IdentifierTag::DELETED_VERTICES },
|
||||
{{"deletedEdges", false}, trigger::IdentifierTag::DELETED_EDGES },
|
||||
{{"assignedVertexProperties", false}, trigger::IdentifierTag::SET_VERTEX_PROPERTIES },
|
||||
{{"assignedEdgeProperties", false}, trigger::IdentifierTag::SET_EDGE_PROPERTIES },
|
||||
{{"removedVertexProperties", false}, trigger::IdentifierTag::REMOVED_VERTEX_PROPERTIES},
|
||||
{{"removedEdgeProperties", false}, trigger::IdentifierTag::REMOVED_EDGE_PROPERTIES },
|
||||
{{"assignedVertexLabels", false}, trigger::IdentifierTag::SET_VERTEX_LABELS },
|
||||
{{"removedVertexLabels", false}, trigger::IdentifierTag::REMOVED_VERTEX_LABELS },
|
||||
{{"updatedVertices", false}, trigger::IdentifierTag::UPDATED_VERTICES },
|
||||
{{"updatedEdges", false}, trigger::IdentifierTag::UPDATED_EDGES },
|
||||
{{"updatedObjects", false}, trigger::IdentifierTag::UPDATED_OBJECTS }};
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
template <typename T>
|
||||
concept WithToMap = requires(const T value, DbAccessor *dba) {
|
||||
{ value.ToMap(dba) }
|
||||
->std::same_as<std::map<std::string, TypedValue>>;
|
||||
};
|
||||
|
||||
template <WithToMap T>
|
||||
TypedValue ToTypedValue(const T &value, DbAccessor *dba) {
|
||||
return TypedValue{value.ToMap(dba)};
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
TypedValue ToTypedValue(const TriggerContext::CreatedObject<TAccessor> &created_object,
|
||||
[[maybe_unused]] DbAccessor *dba) {
|
||||
return TypedValue{created_object.object};
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
TypedValue ToTypedValue(const TriggerContext::DeletedObject<TAccessor> &deleted_object,
|
||||
[[maybe_unused]] DbAccessor *dba) {
|
||||
return TypedValue{deleted_object.object};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept ConvertableToTypedValue = requires(T value) {
|
||||
{TypedValue{value}};
|
||||
concept WithIsValid = requires(const T value) {
|
||||
{ value.IsValid() }
|
||||
->std::same_as<bool>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ConvertableToTypedValue = requires(T value, DbAccessor *dba) {
|
||||
{ ToTypedValue(value, dba) }
|
||||
->std::same_as<TypedValue>;
|
||||
}
|
||||
&&WithIsValid<T>;
|
||||
|
||||
template <typename T>
|
||||
concept LabelUpdateContext = utils::SameAsAnyOf<T, TriggerContext::SetVertexLabel, TriggerContext::RemovedVertexLabel>;
|
||||
|
||||
template <LabelUpdateContext TContext>
|
||||
TypedValue ToTypedValue(const std::vector<TContext> &values, DbAccessor *dba) {
|
||||
std::unordered_map<storage::LabelId, std::vector<TypedValue>> vertices_by_labels;
|
||||
|
||||
for (const auto &value : values) {
|
||||
if (value.IsValid()) {
|
||||
vertices_by_labels[value.label_id].emplace_back(value.object);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, TypedValue> typed_values;
|
||||
for (auto &[label_id, vertices] : vertices_by_labels) {
|
||||
typed_values.emplace(dba->LabelToName(label_id), TypedValue(std::move(vertices)));
|
||||
}
|
||||
|
||||
return TypedValue(std::move(typed_values));
|
||||
}
|
||||
|
||||
template <ConvertableToTypedValue T>
|
||||
TypedValue ToTypedValue(const std::vector<T> &values) {
|
||||
TypedValue ToTypedValue(const std::vector<T> &values, DbAccessor *dba) requires(!LabelUpdateContext<T>) {
|
||||
std::vector<TypedValue> typed_values;
|
||||
typed_values.reserve(values.size());
|
||||
std::transform(std::begin(values), std::end(values), std::back_inserter(typed_values),
|
||||
[](const auto &accessor) { return TypedValue(accessor); });
|
||||
return TypedValue(typed_values);
|
||||
|
||||
for (const auto &value : values) {
|
||||
if (value.IsValid()) {
|
||||
typed_values.push_back(ToTypedValue(value, dba));
|
||||
}
|
||||
}
|
||||
|
||||
return TypedValue(std::move(typed_values));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char *TypeToString() {
|
||||
if constexpr (std::same_as<T, TriggerContext::SetObjectProperty<VertexAccessor>>) {
|
||||
return "set_vertex_property";
|
||||
} else if constexpr (std::same_as<T, TriggerContext::SetObjectProperty<EdgeAccessor>>) {
|
||||
return "set_edge_property";
|
||||
} else if constexpr (std::same_as<T, TriggerContext::RemovedObjectProperty<VertexAccessor>>) {
|
||||
return "removed_vertex_property";
|
||||
} else if constexpr (std::same_as<T, TriggerContext::RemovedObjectProperty<EdgeAccessor>>) {
|
||||
return "removed_edge_property";
|
||||
} else if constexpr (std::same_as<T, TriggerContext::SetVertexLabel>) {
|
||||
return "set_vertex_label";
|
||||
} else if constexpr (std::same_as<T, TriggerContext::RemovedVertexLabel>) {
|
||||
return "removed_vertex_label";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept UpdateContext = WithToMap<T> &&WithIsValid<T>;
|
||||
|
||||
template <UpdateContext... Args>
|
||||
TypedValue Updated(DbAccessor *dba, const std::vector<Args> &...args) {
|
||||
const auto size = (args.size() + ...);
|
||||
std::vector<TypedValue> updated;
|
||||
updated.reserve(size);
|
||||
|
||||
const auto add_to_updated = [&]<UpdateContext T>(const std::vector<T> &values) {
|
||||
for (const auto &value : values) {
|
||||
if (value.IsValid()) {
|
||||
auto map = value.ToMap(dba);
|
||||
map["type"] = TypeToString<T>();
|
||||
updated.emplace_back(std::move(map));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(add_to_updated(args), ...);
|
||||
|
||||
return TypedValue(std::move(updated));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void TriggerContext::RegisterCreatedVertex(const VertexAccessor created_vertex) {
|
||||
created_vertices_.push_back(created_vertex);
|
||||
bool TriggerContext::SetVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
std::map<std::string, TypedValue> TriggerContext::SetVertexLabel::ToMap(DbAccessor *dba) const {
|
||||
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
|
||||
}
|
||||
|
||||
void TriggerContext::RegisterDeletedVertex(const VertexAccessor deleted_vertex) {
|
||||
deleted_vertices_.push_back(deleted_vertex);
|
||||
bool TriggerContext::RemovedVertexLabel::IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
std::map<std::string, TypedValue> TriggerContext::RemovedVertexLabel::ToMap(DbAccessor *dba) const {
|
||||
return {{"vertex", TypedValue{object}}, {"label", TypedValue{dba->LabelToName(label_id)}}};
|
||||
}
|
||||
|
||||
TypedValue TriggerContext::GetTypedValue(const trigger::IdentifierTag tag) const {
|
||||
void TriggerContext::RegisterSetVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id) {
|
||||
set_vertex_labels_.emplace_back(vertex, label_id);
|
||||
}
|
||||
|
||||
void TriggerContext::RegisterRemovedVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id) {
|
||||
removed_vertex_labels_.emplace_back(vertex, label_id);
|
||||
}
|
||||
|
||||
TypedValue TriggerContext::GetTypedValue(const trigger::IdentifierTag tag, DbAccessor *dba) const {
|
||||
const auto &[created_vertices, deleted_vertices, set_vertex_properties, removed_vertex_properties] = vertex_registry_;
|
||||
const auto &[created_edges, deleted_edges, set_edge_properties, removed_edge_properties] = edge_registry_;
|
||||
|
||||
switch (tag) {
|
||||
case trigger::IdentifierTag::CREATED_VERTICES:
|
||||
return ToTypedValue(created_vertices_);
|
||||
return ToTypedValue(created_vertices, dba);
|
||||
|
||||
case trigger::IdentifierTag::CREATED_EDGES:
|
||||
return ToTypedValue(created_edges, dba);
|
||||
|
||||
case trigger::IdentifierTag::DELETED_VERTICES:
|
||||
return ToTypedValue(deleted_vertices_);
|
||||
return ToTypedValue(deleted_vertices, dba);
|
||||
|
||||
case trigger::IdentifierTag::DELETED_EDGES:
|
||||
return ToTypedValue(deleted_edges, dba);
|
||||
|
||||
case trigger::IdentifierTag::SET_VERTEX_PROPERTIES:
|
||||
return ToTypedValue(set_vertex_properties, dba);
|
||||
|
||||
case trigger::IdentifierTag::SET_EDGE_PROPERTIES:
|
||||
return ToTypedValue(set_edge_properties, dba);
|
||||
|
||||
case trigger::IdentifierTag::REMOVED_VERTEX_PROPERTIES:
|
||||
return ToTypedValue(removed_vertex_properties, dba);
|
||||
|
||||
case trigger::IdentifierTag::REMOVED_EDGE_PROPERTIES:
|
||||
return ToTypedValue(removed_edge_properties, dba);
|
||||
|
||||
case trigger::IdentifierTag::SET_VERTEX_LABELS:
|
||||
return ToTypedValue(set_vertex_labels_, dba);
|
||||
|
||||
case trigger::IdentifierTag::REMOVED_VERTEX_LABELS:
|
||||
return ToTypedValue(removed_vertex_labels_, dba);
|
||||
|
||||
case trigger::IdentifierTag::UPDATED_VERTICES:
|
||||
return Updated(dba, set_vertex_properties, removed_vertex_properties, set_vertex_labels_, removed_vertex_labels_);
|
||||
|
||||
case trigger::IdentifierTag::UPDATED_EDGES:
|
||||
return Updated(dba, set_edge_properties, removed_edge_properties);
|
||||
|
||||
case trigger::IdentifierTag::UPDATED_OBJECTS:
|
||||
return Updated(dba, set_vertex_properties, set_edge_properties, removed_vertex_properties,
|
||||
removed_edge_properties, set_vertex_labels_, removed_vertex_labels_);
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
auto &[created_vertices, deleted_vertices, set_vertex_properties, removed_vertex_properties] = vertex_registry_;
|
||||
// adapt created_vertices_
|
||||
auto it = created_vertices_.begin();
|
||||
for (const auto &created_vertex : created_vertices_) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(created_vertex.Gid(), storage::View::OLD); maybe_vertex) {
|
||||
*it = *maybe_vertex;
|
||||
++it;
|
||||
{
|
||||
auto it = created_vertices.begin();
|
||||
for (const auto &created_vertex : created_vertices) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(created_vertex.object.Gid(), storage::View::OLD); maybe_vertex) {
|
||||
*it = CreatedObject{*maybe_vertex};
|
||||
++it;
|
||||
}
|
||||
}
|
||||
created_vertices.erase(it, created_vertices.end());
|
||||
}
|
||||
created_vertices_.erase(it, created_vertices_.end());
|
||||
|
||||
// deleted_vertices_ should keep the transaction context of the transaction which deleted it
|
||||
// because no other transaction can modify an object after it's deleted so it should be the
|
||||
// latest state of the object
|
||||
|
||||
const auto adapt_context_with_vertex = [accessor](auto *values) {
|
||||
auto it = values->begin();
|
||||
for (auto &value : *values) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(value.object.Gid(), storage::View::OLD); maybe_vertex) {
|
||||
*it = std::move(value);
|
||||
it->object = *maybe_vertex;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
values->erase(it, values->end());
|
||||
};
|
||||
|
||||
adapt_context_with_vertex(&set_vertex_properties);
|
||||
adapt_context_with_vertex(&removed_vertex_properties);
|
||||
adapt_context_with_vertex(&set_vertex_labels_);
|
||||
adapt_context_with_vertex(&removed_vertex_labels_);
|
||||
|
||||
auto &[created_edges, deleted_edges, set_edge_properties, removed_edge_properties] = edge_registry_;
|
||||
// adapt created_edges
|
||||
{
|
||||
auto it = created_edges.begin();
|
||||
for (const auto &created_edge : created_edges) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(created_edge.object.From().Gid(), storage::View::OLD);
|
||||
maybe_vertex) {
|
||||
auto maybe_out_edges = maybe_vertex->OutEdges(storage::View::OLD);
|
||||
MG_ASSERT(maybe_out_edges.HasValue());
|
||||
for (const auto &edge : *maybe_out_edges) {
|
||||
if (edge.Gid() == created_edge.object.Gid()) {
|
||||
*it = CreatedObject{edge};
|
||||
++it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
created_edges.erase(it, created_edges.end());
|
||||
}
|
||||
|
||||
// deleted_edges_ should keep the transaction context of the transaction which deleted it
|
||||
// because no other transaction can modify an object after it's deleted so it should be the
|
||||
// latest state of the object
|
||||
|
||||
const auto adapt_context_with_edge = [accessor](auto *values) {
|
||||
auto it = values->begin();
|
||||
for (const auto &value : *values) {
|
||||
if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::View::OLD); maybe_vertex) {
|
||||
auto maybe_out_edges = maybe_vertex->OutEdges(storage::View::OLD);
|
||||
MG_ASSERT(maybe_out_edges.HasValue());
|
||||
for (const auto &edge : *maybe_out_edges) {
|
||||
if (edge.Gid() == value.object.Gid()) {
|
||||
*it = std::move(value);
|
||||
it->object = edge;
|
||||
++it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
values->erase(it, values->end());
|
||||
};
|
||||
|
||||
adapt_context_with_edge(&set_edge_properties);
|
||||
adapt_context_with_edge(&removed_edge_properties);
|
||||
}
|
||||
|
||||
Trigger::Trigger(std::string name, const std::string &query, utils::SkipList<QueryCacheEntry> *query_cache,
|
||||
@ -142,7 +373,7 @@ void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution
|
||||
continue;
|
||||
}
|
||||
|
||||
frame[plan.symbol_table().at(identifier)] = context.GetTypedValue(tag);
|
||||
frame[plan.symbol_table().at(identifier)] = context.GetTypedValue(tag, dba);
|
||||
}
|
||||
|
||||
while (cursor->Pull(frame, ctx))
|
||||
|
@ -1,27 +1,197 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/cypher_query_interpreter.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
namespace trigger {
|
||||
enum class IdentifierTag : uint8_t { CREATED_VERTICES, DELETED_VERTICES };
|
||||
enum class IdentifierTag : uint8_t {
|
||||
CREATED_VERTICES,
|
||||
CREATED_EDGES,
|
||||
DELETED_VERTICES,
|
||||
DELETED_EDGES,
|
||||
SET_VERTEX_PROPERTIES,
|
||||
SET_EDGE_PROPERTIES,
|
||||
REMOVED_VERTEX_PROPERTIES,
|
||||
REMOVED_EDGE_PROPERTIES,
|
||||
SET_VERTEX_LABELS,
|
||||
REMOVED_VERTEX_LABELS,
|
||||
UPDATED_VERTICES,
|
||||
UPDATED_EDGES,
|
||||
UPDATED_OBJECTS
|
||||
};
|
||||
} // namespace trigger
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
concept ObjectAccessor = utils::SameAsAnyOf<T, VertexAccessor, EdgeAccessor>;
|
||||
|
||||
template <ObjectAccessor TAccessor>
|
||||
const char *ObjectString() {
|
||||
if constexpr (std::same_as<TAccessor, VertexAccessor>) {
|
||||
return "vertex";
|
||||
} else {
|
||||
return "edge";
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
struct TriggerContext {
|
||||
void RegisterCreatedVertex(VertexAccessor created_vertex);
|
||||
void RegisterDeletedVertex(VertexAccessor deleted_vertex);
|
||||
static_assert(std::is_trivially_copy_constructible_v<VertexAccessor>,
|
||||
"VertexAccessor is not trivially copy constructible, move it where possible and remove this assert");
|
||||
static_assert(std::is_trivially_copy_constructible_v<EdgeAccessor>,
|
||||
"EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert");
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
void RegisterCreatedObject(const TAccessor &created_object) {
|
||||
GetRegistry<TAccessor>().created_objects_.emplace_back(created_object);
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
void RegisterDeletedObject(const TAccessor &deleted_object) {
|
||||
GetRegistry<TAccessor>().deleted_objects_.emplace_back(deleted_object);
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
void RegisterSetObjectProperty(const TAccessor &object, const storage::PropertyId key, TypedValue old_value,
|
||||
TypedValue new_value) {
|
||||
if (new_value.IsNull()) {
|
||||
RegisterRemovedObjectProperty(object, key, std::move(old_value));
|
||||
return;
|
||||
}
|
||||
|
||||
GetRegistry<TAccessor>().set_object_properties_.emplace_back(object, key, std::move(old_value),
|
||||
std::move(new_value));
|
||||
}
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
void RegisterRemovedObjectProperty(const TAccessor &object, const storage::PropertyId key, TypedValue old_value) {
|
||||
// property is already removed
|
||||
if (old_value.IsNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetRegistry<TAccessor>().removed_object_properties_.emplace_back(object, key, std::move(old_value));
|
||||
}
|
||||
|
||||
void RegisterSetVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
|
||||
void RegisterRemovedVertexLabel(const VertexAccessor &vertex, storage::LabelId label_id);
|
||||
|
||||
// Adapt the TriggerContext object inplace for a different DbAccessor
|
||||
// (each derived accessor, e.g. VertexAccessor, gets adapted
|
||||
// to the sent DbAccessor so they can be used safely)
|
||||
void AdaptForAccessor(DbAccessor *accessor);
|
||||
|
||||
TypedValue GetTypedValue(trigger::IdentifierTag tag) const;
|
||||
TypedValue GetTypedValue(trigger::IdentifierTag tag, DbAccessor *dba) const;
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct CreatedObject {
|
||||
explicit CreatedObject(const TAccessor &object) : object{object} {}
|
||||
|
||||
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
TAccessor object;
|
||||
};
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct DeletedObject {
|
||||
explicit DeletedObject(const TAccessor &object) : object{object} {}
|
||||
|
||||
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
TAccessor object;
|
||||
};
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct SetObjectProperty {
|
||||
explicit SetObjectProperty(const TAccessor &object, storage::PropertyId key, TypedValue old_value,
|
||||
TypedValue new_value)
|
||||
: object{object}, key{key}, old_value{std::move(old_value)}, new_value{std::move(new_value)} {}
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
|
||||
return {{detail::ObjectString<TAccessor>(), TypedValue{object}},
|
||||
{"key", TypedValue{dba->PropertyToName(key)}},
|
||||
{"old", old_value},
|
||||
{"new", new_value}};
|
||||
}
|
||||
|
||||
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
TAccessor object;
|
||||
storage::PropertyId key;
|
||||
TypedValue old_value;
|
||||
TypedValue new_value;
|
||||
};
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct RemovedObjectProperty {
|
||||
explicit RemovedObjectProperty(const TAccessor &object, storage::PropertyId key, TypedValue old_value)
|
||||
: object{object}, key{key}, old_value{std::move(old_value)} {}
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
|
||||
return {{detail::ObjectString<TAccessor>(), TypedValue{object}},
|
||||
{"key", TypedValue{dba->PropertyToName(key)}},
|
||||
{"old", old_value}};
|
||||
}
|
||||
|
||||
bool IsValid() const { return object.IsVisible(storage::View::OLD); }
|
||||
|
||||
TAccessor object;
|
||||
storage::PropertyId key;
|
||||
TypedValue old_value;
|
||||
};
|
||||
|
||||
struct SetVertexLabel {
|
||||
explicit SetVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id)
|
||||
: object{vertex}, label_id{label_id} {}
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const;
|
||||
bool IsValid() const;
|
||||
|
||||
VertexAccessor object;
|
||||
storage::LabelId label_id;
|
||||
};
|
||||
|
||||
struct RemovedVertexLabel {
|
||||
explicit RemovedVertexLabel(const VertexAccessor &vertex, const storage::LabelId label_id)
|
||||
: object{vertex}, label_id{label_id} {}
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const;
|
||||
bool IsValid() const;
|
||||
|
||||
VertexAccessor object;
|
||||
storage::LabelId label_id;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<VertexAccessor> created_vertices_;
|
||||
std::vector<VertexAccessor> deleted_vertices_;
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
struct Registry {
|
||||
std::vector<CreatedObject<TAccessor>> created_objects_;
|
||||
std::vector<DeletedObject<TAccessor>> deleted_objects_;
|
||||
std::vector<SetObjectProperty<TAccessor>> set_object_properties_;
|
||||
std::vector<RemovedObjectProperty<TAccessor>> removed_object_properties_;
|
||||
};
|
||||
|
||||
Registry<VertexAccessor> vertex_registry_;
|
||||
Registry<EdgeAccessor> edge_registry_;
|
||||
|
||||
template <detail::ObjectAccessor TAccessor>
|
||||
Registry<TAccessor> &GetRegistry() {
|
||||
if constexpr (std::same_as<TAccessor, VertexAccessor>) {
|
||||
return vertex_registry_;
|
||||
} else {
|
||||
return edge_registry_;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SetVertexLabel> set_vertex_labels_;
|
||||
std::vector<RemovedVertexLabel> removed_vertex_labels_;
|
||||
};
|
||||
|
||||
struct Trigger {
|
||||
|
@ -3,11 +3,45 @@
|
||||
#include <memory>
|
||||
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace storage {
|
||||
|
||||
bool EdgeAccessor::IsVisible(const View view) const {
|
||||
bool deleted = true;
|
||||
bool exists = true;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
deleted = edge_.ptr->deleted;
|
||||
delta = edge_.ptr->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
VertexAccessor EdgeAccessor::FromVertex() const {
|
||||
return VertexAccessor{from_vertex_, transaction_, indices_, constraints_, config_};
|
||||
}
|
||||
@ -16,7 +50,7 @@ VertexAccessor EdgeAccessor::ToVertex() const {
|
||||
return VertexAccessor{to_vertex_, transaction_, indices_, constraints_, config_};
|
||||
}
|
||||
|
||||
Result<bool> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||
|
||||
@ -27,20 +61,19 @@ Result<bool> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue
|
||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||
|
||||
auto current_value = edge_.ptr->properties.GetProperty(property);
|
||||
bool existed = !current_value.IsNull();
|
||||
// We could skip setting the value if the previous one is the same to the new
|
||||
// one. This would save some memory as a delta would not be created as well as
|
||||
// avoid copying the value. The reason we are not doing that is because the
|
||||
// current code always follows the logical pattern of "create a delta" and
|
||||
// "modify in-place". Additionally, the created delta will make other
|
||||
// transactions get a SERIALIZATION_ERROR.
|
||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(current_value));
|
||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, current_value);
|
||||
edge_.ptr->properties.SetProperty(property, value);
|
||||
|
||||
return !existed;
|
||||
return std::move(current_value);
|
||||
}
|
||||
|
||||
Result<bool> EdgeAccessor::ClearProperties() {
|
||||
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
|
||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
@ -50,14 +83,13 @@ Result<bool> EdgeAccessor::ClearProperties() {
|
||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||
|
||||
auto properties = edge_.ptr->properties.Properties();
|
||||
bool removed = !properties.empty();
|
||||
for (const auto &property : properties) {
|
||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second);
|
||||
}
|
||||
|
||||
edge_.ptr->properties.ClearProperties();
|
||||
|
||||
return removed;
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const {
|
||||
@ -98,7 +130,7 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view)
|
||||
}
|
||||
});
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (deleted) return Error::DELETED_OBJECT;
|
||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||
return std::move(value);
|
||||
}
|
||||
|
||||
@ -149,7 +181,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view)
|
||||
}
|
||||
});
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (deleted) return Error::DELETED_OBJECT;
|
||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class EdgeAccessor final {
|
||||
|
||||
public:
|
||||
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config)
|
||||
Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false)
|
||||
: edge_(edge),
|
||||
edge_type_(edge_type),
|
||||
from_vertex_(from_vertex),
|
||||
@ -31,7 +31,11 @@ class EdgeAccessor final {
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config) {}
|
||||
config_(config),
|
||||
for_deleted_(for_deleted) {}
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const;
|
||||
|
||||
VertexAccessor FromVertex() const;
|
||||
|
||||
@ -39,15 +43,13 @@ class EdgeAccessor final {
|
||||
|
||||
EdgeTypeId EdgeType() const { return edge_type_; }
|
||||
|
||||
/// Set a property value and return `true` if insertion took place.
|
||||
/// `false` is returned if assignment took place.
|
||||
/// Set a property value and return the old value.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
|
||||
/// Remove all properties and return `true` if any removal took place.
|
||||
/// `false` is returned if there were no properties to remove.
|
||||
/// Remove all properties and return old values for each removed property.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> ClearProperties();
|
||||
Result<std::map<PropertyId, PropertyValue>> ClearProperties();
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
||||
@ -79,6 +81,14 @@ class EdgeAccessor final {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
|
||||
// if the accessor was created for a deleted edge.
|
||||
// Accessor behaves differently for some methods based on this
|
||||
// flag.
|
||||
// E.g. If this field is set to true, GetProperty will return the property of the edge
|
||||
// even though the edge is deleted.
|
||||
// All the write operations will still return an error if it's called for a deleted edge.
|
||||
bool for_deleted_{false};
|
||||
};
|
||||
|
||||
} // namespace storage
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/replication/config.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
@ -465,7 +466,7 @@ Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAcce
|
||||
MG_ASSERT(vertex->transaction_ == &transaction_,
|
||||
"VertexAccessor must be from the same transaction as the storage "
|
||||
"accessor when deleting a vertex!");
|
||||
auto vertex_ptr = vertex->vertex_;
|
||||
auto *vertex_ptr = vertex->vertex_;
|
||||
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock);
|
||||
|
||||
@ -484,11 +485,14 @@ Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAcce
|
||||
config_, true);
|
||||
}
|
||||
|
||||
Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Storage::Accessor::DetachDeleteVertex(
|
||||
VertexAccessor *vertex) {
|
||||
using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>;
|
||||
|
||||
MG_ASSERT(vertex->transaction_ == &transaction_,
|
||||
"VertexAccessor must be from the same transaction as the storage "
|
||||
"accessor when deleting a vertex!");
|
||||
auto vertex_ptr = vertex->vertex_;
|
||||
auto *vertex_ptr = vertex->vertex_;
|
||||
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
|
||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
|
||||
@ -498,12 +502,13 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
||||
|
||||
if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (vertex_ptr->deleted) return false;
|
||||
if (vertex_ptr->deleted) return std::optional<ReturnType>{};
|
||||
|
||||
in_edges = vertex_ptr->in_edges;
|
||||
out_edges = vertex_ptr->out_edges;
|
||||
}
|
||||
|
||||
std::vector<EdgeAccessor> deleted_edges;
|
||||
for (const auto &item : in_edges) {
|
||||
auto [edge_type, from_vertex, edge] = item;
|
||||
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_,
|
||||
@ -511,7 +516,11 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
||||
auto ret = DeleteEdge(&e);
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
|
||||
return ret;
|
||||
return ret.GetError();
|
||||
}
|
||||
|
||||
if (ret.GetValue()) {
|
||||
deleted_edges.push_back(*ret.GetValue());
|
||||
}
|
||||
}
|
||||
for (const auto &item : out_edges) {
|
||||
@ -521,7 +530,11 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
||||
auto ret = DeleteEdge(&e);
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
|
||||
return ret;
|
||||
return ret.GetError();
|
||||
}
|
||||
|
||||
if (ret.GetValue()) {
|
||||
deleted_edges.push_back(*ret.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -538,7 +551,9 @@ Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) {
|
||||
CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag());
|
||||
vertex_ptr->deleted = true;
|
||||
|
||||
return true;
|
||||
return std::make_optional<ReturnType>(
|
||||
VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true},
|
||||
std::move(deleted_edges));
|
||||
}
|
||||
|
||||
Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) {
|
||||
@ -668,7 +683,7 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA
|
||||
&storage_->constraints_, config_);
|
||||
}
|
||||
|
||||
Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
Result<std::optional<EdgeAccessor>> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
MG_ASSERT(edge->transaction_ == &transaction_,
|
||||
"EdgeAccessor must be from the same transaction as the storage "
|
||||
"accessor when deleting an edge!");
|
||||
@ -682,11 +697,11 @@ Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
|
||||
if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR;
|
||||
|
||||
if (edge_ptr->deleted) return false;
|
||||
if (edge_ptr->deleted) return std::optional<EdgeAccessor>{};
|
||||
}
|
||||
|
||||
auto from_vertex = edge->from_vertex_;
|
||||
auto to_vertex = edge->to_vertex_;
|
||||
auto *from_vertex = edge->from_vertex_;
|
||||
auto *to_vertex = edge->to_vertex_;
|
||||
|
||||
// Obtain the locks by `gid` order to avoid lock cycles.
|
||||
std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock);
|
||||
@ -732,12 +747,12 @@ Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!");
|
||||
if (!op1 && !op2) {
|
||||
// The edge is already deleted.
|
||||
return false;
|
||||
return std::optional<EdgeAccessor>{};
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.properties_on_edges) {
|
||||
auto edge_ptr = edge_ref.ptr;
|
||||
auto *edge_ptr = edge_ref.ptr;
|
||||
CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag());
|
||||
edge_ptr->deleted = true;
|
||||
}
|
||||
@ -748,7 +763,8 @@ Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
// Decrement edge count.
|
||||
storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel);
|
||||
|
||||
return true;
|
||||
return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_,
|
||||
&storage_->indices_, &storage_->constraints_, config_, true);
|
||||
}
|
||||
|
||||
const std::string &Storage::Accessor::LabelToName(LabelId label) const { return storage_->LabelToName(label); }
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "storage/v2/commit_log.hpp"
|
||||
@ -249,17 +250,21 @@ class Storage final {
|
||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
|
||||
}
|
||||
|
||||
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex);
|
||||
|
||||
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> DetachDeleteVertex(VertexAccessor *vertex);
|
||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
||||
VertexAccessor *vertex);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type);
|
||||
|
||||
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> DeleteEdge(EdgeAccessor *edge);
|
||||
Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
||||
|
||||
const std::string &LabelToName(LabelId label) const;
|
||||
const std::string &PropertyToName(PropertyId property) const;
|
||||
|
@ -6,21 +6,24 @@
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace storage {
|
||||
|
||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view) {
|
||||
bool is_visible = true;
|
||||
namespace detail {
|
||||
namespace {
|
||||
std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View view) {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||
is_visible = !vertex->deleted;
|
||||
deleted = vertex->deleted;
|
||||
delta = vertex->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction, delta, view, [&is_visible](const Delta &delta) {
|
||||
ApplyDeltasForRead(transaction, delta, view, [&](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
@ -31,19 +34,35 @@ std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
is_visible = true;
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
is_visible = false;
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!is_visible) return std::nullopt;
|
||||
|
||||
return {exists, deleted};
|
||||
}
|
||||
} // namespace
|
||||
} // namespace detail
|
||||
|
||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view) {
|
||||
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return VertexAccessor{vertex, transaction, indices, constraints, config};
|
||||
}
|
||||
|
||||
bool VertexAccessor::IsVisible(View view) const {
|
||||
const auto [exists, deleted] = detail::IsVisible(vertex_, transaction_, view);
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
@ -177,7 +196,7 @@ Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
|
||||
return std::move(labels);
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
@ -186,22 +205,21 @@ Result<bool> VertexAccessor::SetProperty(PropertyId property, const PropertyValu
|
||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||
|
||||
auto current_value = vertex_->properties.GetProperty(property);
|
||||
bool existed = !current_value.IsNull();
|
||||
// We could skip setting the value if the previous one is the same to the new
|
||||
// one. This would save some memory as a delta would not be created as well as
|
||||
// avoid copying the value. The reason we are not doing that is because the
|
||||
// current code always follows the logical pattern of "create a delta" and
|
||||
// "modify in-place". Additionally, the created delta will make other
|
||||
// transactions get a SERIALIZATION_ERROR.
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, std::move(current_value));
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
|
||||
vertex_->properties.SetProperty(property, value);
|
||||
|
||||
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
||||
|
||||
return !existed;
|
||||
return std::move(current_value);
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::ClearProperties() {
|
||||
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||
@ -209,7 +227,6 @@ Result<bool> VertexAccessor::ClearProperties() {
|
||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||
|
||||
auto properties = vertex_->properties.Properties();
|
||||
bool removed = !properties.empty();
|
||||
for (const auto &property : properties) {
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property.first, property.second);
|
||||
UpdateOnSetProperty(indices_, property.first, PropertyValue(), vertex_, *transaction_);
|
||||
@ -217,7 +234,7 @@ Result<bool> VertexAccessor::ClearProperties() {
|
||||
|
||||
vertex_->properties.ClearProperties();
|
||||
|
||||
return removed;
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view) const {
|
||||
|
@ -33,6 +33,9 @@ class VertexAccessor final {
|
||||
static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view);
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const;
|
||||
|
||||
/// Add a label and return `true` if insertion took place.
|
||||
/// `false` is returned if the label already existed.
|
||||
/// @throw std::bad_alloc
|
||||
@ -50,15 +53,13 @@ class VertexAccessor final {
|
||||
/// std::vector::max_size().
|
||||
Result<std::vector<LabelId>> Labels(View view) const;
|
||||
|
||||
/// Set a property value and return `true` if insertion took place.
|
||||
/// `false` is returned if assignment took place.
|
||||
/// Set a property value and return the old value.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
|
||||
/// Remove all properties and return `true` if any removal took place.
|
||||
/// `false` is returned if there were no properties to remove.
|
||||
/// Remove all properties and return the values of the removed properties.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> ClearProperties();
|
||||
Result<std::map<PropertyId, PropertyValue>> ClearProperties();
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
||||
|
7
src/utils/concepts.hpp
Normal file
7
src/utils/concepts.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
|
||||
namespace utils {
|
||||
template <typename T, typename... Args>
|
||||
concept SameAsAnyOf = (std::same_as<T, Args> || ...);
|
||||
} // namespace utils
|
@ -120,9 +120,9 @@ TEST(Storage, LabelPropertyIndex) {
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
{
|
||||
auto ret = vertex.SetProperty(prop, storage::PropertyValue(vertex.Gid().AsInt()));
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
auto old_value = vertex.SetProperty(prop, storage::PropertyValue(vertex.Gid().AsInt()));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
@ -164,9 +164,9 @@ TEST(Storage, LabelPropertyIndex) {
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
{
|
||||
auto ret = vertex.SetProperty(prop, storage::PropertyValue(vertex.Gid().AsInt()));
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
auto old_value = vertex.SetProperty(prop, storage::PropertyValue(vertex.Gid().AsInt()));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ TEST(StorageV2, VertexDeleteProperty) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
// Set property 5 to "nandare"
|
||||
ASSERT_TRUE(vertex->SetProperty(property, storage::PropertyValue("nandare")).GetValue());
|
||||
ASSERT_TRUE(vertex->SetProperty(property, storage::PropertyValue("nandare"))->IsNull());
|
||||
|
||||
// Check whether property 5 exists
|
||||
ASSERT_TRUE(vertex->GetProperty(property, storage::View::OLD)->IsNull());
|
||||
@ -801,7 +801,7 @@ TEST(StorageV2, VertexDeleteProperty) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
// Set property 5 to "nandare"
|
||||
ASSERT_TRUE(vertex->SetProperty(property, storage::PropertyValue("nandare")).GetValue());
|
||||
ASSERT_TRUE(vertex->SetProperty(property, storage::PropertyValue("nandare"))->IsNull());
|
||||
|
||||
// Check whether property 5 exists
|
||||
ASSERT_TRUE(vertex->GetProperty(property, storage::View::OLD)->IsNull());
|
||||
@ -1349,9 +1349,9 @@ TEST(StorageV2, VertexPropertyCommit) {
|
||||
ASSERT_EQ(vertex.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = vertex.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex.GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -1362,9 +1362,9 @@ TEST(StorageV2, VertexPropertyCommit) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = vertex.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex.GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -1412,9 +1412,9 @@ TEST(StorageV2, VertexPropertyCommit) {
|
||||
auto property = acc.NameToProperty("property5");
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -1428,9 +1428,9 @@ TEST(StorageV2, VertexPropertyCommit) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
@ -1481,9 +1481,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -1494,9 +1494,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -1542,9 +1542,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -1555,9 +1555,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -1623,9 +1623,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -1694,9 +1694,9 @@ TEST(StorageV2, VertexPropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(vertex->GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -1764,9 +1764,9 @@ TEST(StorageV2, VertexPropertySerializationError) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = vertex->SetProperty(property1, storage::PropertyValue(123));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property1, storage::PropertyValue(123));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(vertex->GetProperty(property1, storage::View::OLD)->IsNull());
|
||||
@ -1886,7 +1886,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) {
|
||||
ASSERT_EQ(vertex.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
// Set property 5 to "nandare"
|
||||
ASSERT_TRUE(vertex.SetProperty(property, storage::PropertyValue("nandare")).GetValue());
|
||||
ASSERT_TRUE(vertex.SetProperty(property, storage::PropertyValue("nandare"))->IsNull());
|
||||
|
||||
// Check whether label 5 and property 5 exist
|
||||
ASSERT_TRUE(vertex.HasLabel(label, storage::View::OLD).GetValue());
|
||||
@ -1940,7 +1940,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) {
|
||||
}
|
||||
|
||||
// Set property 5 to "haihai"
|
||||
ASSERT_FALSE(vertex.SetProperty(property, storage::PropertyValue("haihai")).GetValue());
|
||||
ASSERT_FALSE(vertex.SetProperty(property, storage::PropertyValue("haihai"))->IsNull());
|
||||
|
||||
// Check whether label 5 and property 5 exist
|
||||
ASSERT_TRUE(vertex.HasLabel(label, storage::View::OLD).GetValue());
|
||||
@ -2044,7 +2044,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) {
|
||||
}
|
||||
|
||||
// Set property 5 to null
|
||||
ASSERT_FALSE(vertex.SetProperty(property, storage::PropertyValue()).GetValue());
|
||||
ASSERT_FALSE(vertex.SetProperty(property, storage::PropertyValue())->IsNull());
|
||||
|
||||
// Check whether label 5 and property 5 exist
|
||||
ASSERT_FALSE(vertex.HasLabel(label, storage::View::OLD).GetValue());
|
||||
@ -2086,9 +2086,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
auto vertex = acc.CreateVertex();
|
||||
gid = vertex.Gid();
|
||||
|
||||
auto res = vertex.SetProperty(property1, storage::PropertyValue("value"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex.SetProperty(property1, storage::PropertyValue("value"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
@ -2103,9 +2103,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
UnorderedElementsAre(std::pair(property1, storage::PropertyValue("value"))));
|
||||
|
||||
{
|
||||
auto ret = vertex->ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
auto old_values = vertex->ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_FALSE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(vertex->GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -2113,9 +2113,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW).GetValue().size(), 0);
|
||||
|
||||
{
|
||||
auto ret = vertex->ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_FALSE(ret.GetValue());
|
||||
auto old_values = vertex->ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_TRUE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(vertex->GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -2129,9 +2129,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
auto vertex = acc.FindVertex(gid, storage::View::OLD);
|
||||
ASSERT_TRUE(vertex);
|
||||
|
||||
auto res = vertex->SetProperty(property2, storage::PropertyValue(42));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = vertex->SetProperty(property2, storage::PropertyValue(42));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
@ -2147,9 +2147,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
std::pair(property2, storage::PropertyValue(42))));
|
||||
|
||||
{
|
||||
auto ret = vertex->ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
auto old_values = vertex->ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_FALSE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(vertex->GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -2157,9 +2157,9 @@ TEST(StorageV2, VertexPropertyClear) {
|
||||
ASSERT_EQ(vertex->Properties(storage::View::NEW).GetValue().size(), 0);
|
||||
|
||||
{
|
||||
auto ret = vertex->ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_FALSE(ret.GetValue());
|
||||
auto old_values = vertex->ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_TRUE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(vertex->GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
|
@ -3324,7 +3324,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex_from);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -3543,7 +3543,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex1);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -3791,7 +3791,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex_from);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -3895,7 +3895,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex_from);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -4114,7 +4114,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex1);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -4430,7 +4430,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) {
|
||||
{
|
||||
auto ret = acc.DetachDeleteVertex(&*vertex1);
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
ASSERT_TRUE(*ret);
|
||||
}
|
||||
|
||||
// Check edges
|
||||
@ -4622,9 +4622,9 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -4635,9 +4635,9 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -4687,9 +4687,9 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
|
||||
auto property = acc.NameToProperty("property5");
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -4703,9 +4703,9 @@ TEST(StorageWithProperties, EdgePropertyCommit) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
@ -4763,9 +4763,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -4776,9 +4776,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -4826,9 +4826,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("temporary"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "temporary");
|
||||
@ -4839,9 +4839,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue("nandare"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::NEW)->ValueString(), "nandare");
|
||||
@ -4909,9 +4909,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -4982,9 +4982,9 @@ TEST(StorageWithProperties, EdgePropertyAbort) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_FALSE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property, storage::PropertyValue());
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_FALSE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_EQ(edge.GetProperty(property, storage::View::OLD)->ValueString(), "nandare");
|
||||
@ -5059,9 +5059,9 @@ TEST(StorageWithProperties, EdgePropertySerializationError) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW)->size(), 0);
|
||||
|
||||
{
|
||||
auto res = edge.SetProperty(property1, storage::PropertyValue(123));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property1, storage::PropertyValue(123));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(edge.GetProperty(property1, storage::View::OLD)->IsNull());
|
||||
@ -5148,9 +5148,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
ASSERT_EQ(edge.FromVertex(), vertex);
|
||||
ASSERT_EQ(edge.ToVertex(), vertex);
|
||||
|
||||
auto res = edge.SetProperty(property1, storage::PropertyValue("value"));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property1, storage::PropertyValue("value"));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
@ -5166,9 +5166,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
UnorderedElementsAre(std::pair(property1, storage::PropertyValue("value"))));
|
||||
|
||||
{
|
||||
auto ret = edge.ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
auto old_values = edge.ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_FALSE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(edge.GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -5176,9 +5176,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW).GetValue().size(), 0);
|
||||
|
||||
{
|
||||
auto ret = edge.ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_FALSE(ret.GetValue());
|
||||
auto old_values = edge.ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_TRUE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(edge.GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -5193,9 +5193,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
ASSERT_TRUE(vertex);
|
||||
auto edge = vertex->OutEdges(storage::View::NEW).GetValue()[0];
|
||||
|
||||
auto res = edge.SetProperty(property2, storage::PropertyValue(42));
|
||||
ASSERT_TRUE(res.HasValue());
|
||||
ASSERT_TRUE(res.GetValue());
|
||||
auto old_value = edge.SetProperty(property2, storage::PropertyValue(42));
|
||||
ASSERT_TRUE(old_value.HasValue());
|
||||
ASSERT_TRUE(old_value->IsNull());
|
||||
|
||||
ASSERT_FALSE(acc.Commit().HasError());
|
||||
}
|
||||
@ -5212,9 +5212,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
std::pair(property2, storage::PropertyValue(42))));
|
||||
|
||||
{
|
||||
auto ret = edge.ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_TRUE(ret.GetValue());
|
||||
auto old_values = edge.ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_FALSE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(edge.GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -5222,9 +5222,9 @@ TEST(StorageWithProperties, EdgePropertyClear) {
|
||||
ASSERT_EQ(edge.Properties(storage::View::NEW).GetValue().size(), 0);
|
||||
|
||||
{
|
||||
auto ret = edge.ClearProperties();
|
||||
ASSERT_TRUE(ret.HasValue());
|
||||
ASSERT_FALSE(ret.GetValue());
|
||||
auto old_values = edge.ClearProperties();
|
||||
ASSERT_TRUE(old_values.HasValue());
|
||||
ASSERT_TRUE(old_values->empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(edge.GetProperty(property1, storage::View::NEW)->IsNull());
|
||||
@ -5361,7 +5361,7 @@ TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) {
|
||||
ASSERT_EQ(*edge->GetProperty(property, storage::View::NEW), storage::PropertyValue());
|
||||
|
||||
// Modify edge.
|
||||
ASSERT_TRUE(edge->SetProperty(property, storage::PropertyValue("value")).HasValue());
|
||||
ASSERT_TRUE(edge->SetProperty(property, storage::PropertyValue("value"))->IsNull());
|
||||
|
||||
// Check state after (OLD view).
|
||||
ASSERT_EQ(edge->Properties(storage::View::OLD).GetError(), storage::Error::NONEXISTENT_OBJECT);
|
||||
|
Loading…
Reference in New Issue
Block a user