Other predefined variables (#143)

This commit is contained in:
antonio2368 2021-05-10 10:10:01 +02:00 committed by Antonio Andelic
parent 11c0dde11c
commit b459639968
17 changed files with 933 additions and 288 deletions

View File

@ -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) {

View File

@ -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());
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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))

View File

@ -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 {

View File

@ -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);
}

View File

@ -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

View File

@ -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); }

View File

@ -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;

View File

@ -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 {

View File

@ -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
View 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

View File

@ -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());
}

View File

@ -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());

View File

@ -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);