From b1f7bbf0513670cf0875f61ee303ae9681ac7d43 Mon Sep 17 00:00:00 2001 From: Matej Ferencevic Date: Mon, 8 Jul 2019 15:36:06 +0200 Subject: [PATCH] Implement properties for edges in storage v2 Reviewers: mtomic, teon.banek Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2187 --- src/storage/v2/edge.hpp | 3 +- src/storage/v2/edge_accessor.cpp | 129 ++++++++ src/storage/v2/edge_accessor.hpp | 6 + src/storage/v2/storage.cpp | 17 +- tests/unit/storage_v2_edge.cpp | 522 +++++++++++++++++++++++++++++++ 5 files changed, 674 insertions(+), 3 deletions(-) diff --git a/src/storage/v2/edge.hpp b/src/storage/v2/edge.hpp index 841cab886..85a2da1d3 100644 --- a/src/storage/v2/edge.hpp +++ b/src/storage/v2/edge.hpp @@ -20,8 +20,7 @@ struct Edge { Gid gid; - // TODO: add - // std::unordered_map properties; + std::unordered_map properties; utils::SpinLock lock; bool deleted; diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp index 3cd753edc..8dd5e8740 100644 --- a/src/storage/v2/edge_accessor.cpp +++ b/src/storage/v2/edge_accessor.cpp @@ -15,4 +15,133 @@ VertexAccessor EdgeAccessor::ToVertex() { return VertexAccessor{to_vertex_, transaction_}; } +Result EdgeAccessor::SetProperty(uint64_t property, + const PropertyValue &value) { + std::lock_guard guard(edge_->lock); + + if (!PrepareForWrite(transaction_, edge_)) + return Result{Error::SERIALIZATION_ERROR}; + + if (edge_->deleted) return Result{Error::DELETED_OBJECT}; + + auto it = edge_->properties.find(property); + bool existed = it != edge_->properties.end(); + if (it != edge_->properties.end()) { + CreateAndLinkDelta(transaction_, edge_, Delta::SetPropertyTag(), property, + it->second); + if (value.IsNull()) { + // remove the property + edge_->properties.erase(it); + } else { + // set the value + it->second = value; + } + } else { + CreateAndLinkDelta(transaction_, edge_, Delta::SetPropertyTag(), property, + PropertyValue()); + if (!value.IsNull()) { + edge_->properties.emplace(property, value); + } + } + + return Result{existed}; +} + +Result EdgeAccessor::GetProperty(uint64_t property, View view) { + bool deleted = false; + PropertyValue value; + Delta *delta = nullptr; + { + std::lock_guard guard(edge_->lock); + deleted = edge_->deleted; + auto it = edge_->properties.find(property); + if (it != edge_->properties.end()) { + value = it->second; + } + delta = edge_->delta; + } + ApplyDeltasForRead(transaction_, delta, view, + [&deleted, &value, property](const Delta &delta) { + switch (delta.action) { + case Delta::Action::SET_PROPERTY: { + if (delta.property.key == property) { + value = delta.property.value; + } + break; + } + case Delta::Action::DELETE_OBJECT: { + LOG(FATAL) << "Invalid accessor!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + 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; + } + }); + if (deleted) return Result{Error::DELETED_OBJECT}; + return Result{std::move(value)}; +} + +Result> EdgeAccessor::Properties( + View view) { + std::unordered_map properties; + bool deleted = false; + Delta *delta = nullptr; + { + std::lock_guard guard(edge_->lock); + deleted = edge_->deleted; + properties = edge_->properties; + delta = edge_->delta; + } + ApplyDeltasForRead( + transaction_, delta, view, [&deleted, &properties](const Delta &delta) { + switch (delta.action) { + case Delta::Action::SET_PROPERTY: { + auto it = properties.find(delta.property.key); + if (it != properties.end()) { + if (delta.property.value.IsNull()) { + // remove the property + properties.erase(it); + } else { + // set the value + it->second = delta.property.value; + } + } else if (!delta.property.value.IsNull()) { + properties.emplace(delta.property.key, delta.property.value); + } + break; + } + case Delta::Action::DELETE_OBJECT: { + LOG(FATAL) << "Invalid accessor!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + 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; + } + }); + if (deleted) { + return Result>{ + Error::DELETED_OBJECT}; + } + return Result>{ + std::move(properties)}; +} + } // namespace storage diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index 0f9d9e537..30f0af6f7 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -33,6 +33,12 @@ class EdgeAccessor final { uint64_t EdgeType() const { return edge_type_; } + Result SetProperty(uint64_t property, const PropertyValue &value); + + Result GetProperty(uint64_t property, View view); + + Result> Properties(View view); + Gid Gid() const { return edge_->gid; } bool operator==(const EdgeAccessor &other) const { diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 82a843814..e5969b190 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -407,6 +407,22 @@ void Storage::Accessor::Abort() { current->timestamp->load(std::memory_order_acquire) == transaction_->transaction_id) { switch (current->action) { + case Delta::Action::SET_PROPERTY: { + auto it = edge->properties.find(current->property.key); + if (it != edge->properties.end()) { + if (current->property.value.IsNull()) { + // remove the property + edge->properties.erase(it); + } else { + // set the value + it->second = current->property.value; + } + } else if (!current->property.value.IsNull()) { + edge->properties.emplace(current->property.key, + current->property.value); + } + break; + } case Delta::Action::DELETE_OBJECT: { auto acc = storage_->edges_.access(); CHECK(acc.remove(edge->gid)) << "Invalid database state!"; @@ -418,7 +434,6 @@ void Storage::Accessor::Abort() { } case Delta::Action::REMOVE_LABEL: case Delta::Action::ADD_LABEL: - case Delta::Action::SET_PROPERTY: case Delta::Action::ADD_IN_EDGE: case Delta::Action::ADD_OUT_EDGE: case Delta::Action::REMOVE_IN_EDGE: diff --git a/tests/unit/storage_v2_edge.cpp b/tests/unit/storage_v2_edge.cpp index 0a49674ca..1b25df2ef 100644 --- a/tests/unit/storage_v2_edge.cpp +++ b/tests/unit/storage_v2_edge.cpp @@ -4622,3 +4622,525 @@ TEST(StorageV2, VertexDetachDeleteMultipleAbort) { } } } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgePropertyCommit) { + storage::Storage store; + storage::Gid gid = + storage::Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + auto edge = acc.CreateEdge(&vertex, &vertex, 5).GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(5, storage::PropertyValue("temporary")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_FALSE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "temporary"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "temporary"); + } + + { + auto res = edge.SetProperty(5, storage::PropertyValue("nandare")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + acc.Commit(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + { + auto res = edge.SetProperty(5, storage::PropertyValue()); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(5, storage::PropertyValue()); + ASSERT_TRUE(res.IsReturn()); + ASSERT_FALSE(res.GetReturn()); + } + + acc.Commit(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(5, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgePropertyAbort) { + storage::Storage store; + storage::Gid gid = + storage::Gid::FromUint(std::numeric_limits::max()); + + // Create the vertex. + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + auto edge = acc.CreateEdge(&vertex, &vertex, 5).GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + acc.Commit(); + } + + // Set property 5 to "nandare", but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(5, storage::PropertyValue("temporary")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_FALSE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "temporary"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "temporary"); + } + + { + auto res = edge.SetProperty(5, storage::PropertyValue("nandare")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + acc.Abort(); + } + + // Check that property 5 is null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(5, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } + + // Set property 5 to "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(5, storage::PropertyValue("temporary")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_FALSE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "temporary"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "temporary"); + } + + { + auto res = edge.SetProperty(5, storage::PropertyValue("nandare")); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + acc.Commit(); + } + + // Check that property 5 is "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } + + // Set property 5 to null, but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + { + auto res = edge.SetProperty(5, storage::PropertyValue()); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + acc.Abort(); + } + + // Check that property 5 is "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } + + // Set property 5 to null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::NEW).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + { + auto res = edge.SetProperty(5, storage::PropertyValue()); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + } + + ASSERT_EQ(edge.GetProperty(5, storage::View::OLD).GetReturn().ValueString(), + "nandare"); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[5].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } + + // Check that property 5 is null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(5, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(5, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + ASSERT_TRUE(edge.GetProperty(10, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(10, storage::View::NEW).GetReturn().IsNull()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgePropertySerializationError) { + storage::Storage store; + storage::Gid gid = + storage::Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + auto edge = acc.CreateEdge(&vertex, &vertex, 5).GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + acc.Commit(); + } + + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + // Set property 1 to 123 in accessor 1. + { + auto vertex = acc1.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(1, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(1, storage::View::NEW).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(2, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(2, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(1, storage::PropertyValue(123)); + ASSERT_TRUE(res.IsReturn()); + ASSERT_FALSE(res.GetReturn()); + } + + ASSERT_TRUE(edge.GetProperty(1, storage::View::OLD).GetReturn().IsNull()); + ASSERT_EQ(edge.GetProperty(1, storage::View::NEW).GetReturn().ValueInt(), + 123); + ASSERT_TRUE(edge.GetProperty(2, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(2, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[1].ValueInt(), 123); + } + } + + // Set property 2 to "nandare" in accessor 2. + { + auto vertex = acc2.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_TRUE(edge.GetProperty(1, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(1, storage::View::NEW).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(2, storage::View::OLD).GetReturn().IsNull()); + ASSERT_TRUE(edge.GetProperty(2, storage::View::NEW).GetReturn().IsNull()); + ASSERT_EQ(edge.Properties(storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(edge.Properties(storage::View::NEW).GetReturn().size(), 0); + + { + auto res = edge.SetProperty(2, storage::PropertyValue("nandare")); + ASSERT_TRUE(res.IsError()); + ASSERT_EQ(res.GetError(), storage::Error::SERIALIZATION_ERROR); + } + } + + // Finalize both accessors. + acc1.Commit(); + acc2.Abort(); + + // Check which properties exist. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, storage::View::OLD); + ASSERT_TRUE(vertex); + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + ASSERT_EQ(edge.GetProperty(1, storage::View::OLD).GetReturn().ValueInt(), + 123); + ASSERT_TRUE(edge.GetProperty(2, storage::View::OLD).GetReturn().IsNull()); + { + auto properties = edge.Properties(storage::View::OLD).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[1].ValueInt(), 123); + } + + ASSERT_EQ(edge.GetProperty(1, storage::View::NEW).GetReturn().ValueInt(), + 123); + ASSERT_TRUE(edge.GetProperty(2, storage::View::NEW).GetReturn().IsNull()); + { + auto properties = edge.Properties(storage::View::NEW).GetReturn(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[1].ValueInt(), 123); + } + + acc.Abort(); + } +}