memgraph/tests/unit/storage_v2_durability.cpp
Matej Ferencevic 3adedc0679 Implement snapshots for storage v2
Reviewers: teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2400
2019-10-01 13:42:37 +02:00

765 lines
27 KiB
C++

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <thread>
#include "storage/v2/durability.hpp"
#include "storage/v2/storage.hpp"
#include "utils/file.hpp"
using testing::Contains;
using testing::UnorderedElementsAre;
class DurabilityTest : public ::testing::TestWithParam<bool> {
private:
const uint64_t kNumBaseVertices = 1000;
const uint64_t kNumBaseEdges = 10000;
const uint64_t kNumExtendedVertices = 100;
const uint64_t kNumExtendedEdges = 1000;
public:
DurabilityTest()
: base_vertex_gids_(kNumBaseVertices),
base_edge_gids_(kNumBaseEdges),
extended_vertex_gids_(kNumExtendedVertices),
extended_edge_gids_(kNumExtendedEdges) {}
void SetUp() override { Clear(); }
void TearDown() override { Clear(); }
void CreateBaseDataset(storage::Storage *store, bool properties_on_edges) {
auto label_indexed = store->NameToLabel("base_indexed");
auto label_unindexed = store->NameToLabel("base_unindexed");
auto property_id = store->NameToProperty("id");
auto et1 = store->NameToEdgeType("base_et1");
auto et2 = store->NameToEdgeType("base_et2");
// Create label index.
ASSERT_TRUE(store->CreateIndex(label_unindexed));
// Create label+property index.
ASSERT_TRUE(store->CreateIndex(label_indexed, property_id));
// Create existence constraint.
ASSERT_FALSE(store->CreateExistenceConstraint(label_unindexed, property_id)
.HasError());
// Create vertices.
for (uint64_t i = 0; i < kNumBaseVertices; ++i) {
auto acc = store->Access();
auto vertex = acc.CreateVertex();
base_vertex_gids_[i] = vertex.Gid();
if (i < kNumBaseVertices / 2) {
ASSERT_TRUE(vertex.AddLabel(label_indexed).HasValue());
} else {
ASSERT_TRUE(vertex.AddLabel(label_unindexed).HasValue());
}
if (i < kNumBaseVertices / 3 || i >= kNumBaseVertices / 2) {
ASSERT_TRUE(vertex
.SetProperty(property_id, storage::PropertyValue(
static_cast<int64_t>(i)))
.HasValue());
}
ASSERT_FALSE(acc.Commit().HasError());
}
// Create edges.
for (uint64_t i = 0; i < kNumBaseEdges; ++i) {
auto acc = store->Access();
auto vertex1 = acc.FindVertex(
base_vertex_gids_[(i / 2) % kNumBaseVertices], storage::View::OLD);
ASSERT_TRUE(vertex1);
auto vertex2 = acc.FindVertex(
base_vertex_gids_[(i / 3) % kNumBaseVertices], storage::View::OLD);
ASSERT_TRUE(vertex2);
storage::EdgeTypeId et;
if (i < kNumBaseEdges / 2) {
et = et1;
} else {
et = et2;
}
auto edge = acc.CreateEdge(&*vertex1, &*vertex2, et);
ASSERT_TRUE(edge.HasValue());
base_edge_gids_[i] = edge->Gid();
if (properties_on_edges) {
ASSERT_TRUE(edge->SetProperty(property_id, storage::PropertyValue(
static_cast<int64_t>(i)))
.HasValue());
} else {
auto ret = edge->SetProperty(
property_id, storage::PropertyValue(static_cast<int64_t>(i)));
ASSERT_TRUE(ret.HasError());
ASSERT_EQ(ret.GetError(), storage::Error::PROPERTIES_DISABLED);
}
ASSERT_FALSE(acc.Commit().HasError());
}
}
void VerifyBaseDataset(storage::Storage *store, bool properties_on_edges,
bool extended_dataset_exists) {
auto label_indexed = store->NameToLabel("base_indexed");
auto label_unindexed = store->NameToLabel("base_unindexed");
auto property_id = store->NameToProperty("id");
auto et1 = store->NameToEdgeType("base_et1");
auto et2 = store->NameToEdgeType("base_et2");
// Verify indices info.
{
auto info = store->ListAllIndices();
if (extended_dataset_exists) {
ASSERT_THAT(info.label, Contains(label_unindexed));
ASSERT_THAT(info.label_property,
Contains(std::make_pair(label_indexed, property_id)));
} else {
ASSERT_THAT(info.label, UnorderedElementsAre(label_unindexed));
ASSERT_THAT(info.label_property, UnorderedElementsAre(std::make_pair(
label_indexed, property_id)));
}
}
// Verify constraints info.
{
auto info = store->ListAllConstraints();
if (extended_dataset_exists) {
ASSERT_THAT(info.existence,
Contains(std::make_pair(label_unindexed, property_id)));
} else {
ASSERT_THAT(info.existence, UnorderedElementsAre(std::make_pair(
label_unindexed, property_id)));
}
}
// Create storage accessor.
auto acc = store->Access();
// Verify vertices.
for (uint64_t i = 0; i < kNumBaseVertices; ++i) {
auto vertex = acc.FindVertex(base_vertex_gids_[i], storage::View::OLD);
ASSERT_TRUE(vertex);
auto labels = vertex->Labels(storage::View::OLD);
ASSERT_TRUE(labels.HasValue());
if (i < kNumBaseVertices / 2) {
ASSERT_THAT(*labels, UnorderedElementsAre(label_indexed));
} else {
ASSERT_THAT(*labels, UnorderedElementsAre(label_unindexed));
}
auto properties = vertex->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
if (i < kNumBaseVertices / 3 || i >= kNumBaseVertices / 2) {
ASSERT_EQ(properties->size(), 1);
ASSERT_EQ((*properties)[property_id],
storage::PropertyValue(static_cast<int64_t>(i)));
} else {
ASSERT_EQ(properties->size(), 0);
}
}
// Verify edges.
for (uint64_t i = 0; i < kNumBaseEdges; ++i) {
auto find_edge =
[&](const auto &edges) -> std::optional<storage::EdgeAccessor> {
for (const auto &edge : edges) {
if (edge.Gid() == base_edge_gids_[i]) {
return edge;
}
}
return std::nullopt;
};
{
auto vertex1 = acc.FindVertex(
base_vertex_gids_[(i / 2) % kNumBaseVertices], storage::View::OLD);
ASSERT_TRUE(vertex1);
auto out_edges = vertex1->OutEdges({}, storage::View::OLD);
ASSERT_TRUE(out_edges.HasValue());
auto edge1 = find_edge(*out_edges);
ASSERT_TRUE(edge1);
if (i < kNumBaseEdges / 2) {
ASSERT_EQ(edge1->EdgeType(), et1);
} else {
ASSERT_EQ(edge1->EdgeType(), et2);
}
auto properties = edge1->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
if (properties_on_edges) {
ASSERT_EQ(properties->size(), 1);
ASSERT_EQ((*properties)[property_id],
storage::PropertyValue(static_cast<int64_t>(i)));
} else {
ASSERT_EQ(properties->size(), 0);
}
}
{
auto vertex2 = acc.FindVertex(
base_vertex_gids_[(i / 3) % kNumBaseVertices], storage::View::OLD);
ASSERT_TRUE(vertex2);
auto in_edges = vertex2->InEdges({}, storage::View::OLD);
ASSERT_TRUE(in_edges.HasValue());
auto edge2 = find_edge(*in_edges);
ASSERT_TRUE(edge2);
if (i < kNumBaseEdges / 2) {
ASSERT_EQ(edge2->EdgeType(), et1);
} else {
ASSERT_EQ(edge2->EdgeType(), et2);
}
auto properties = edge2->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
if (properties_on_edges) {
ASSERT_EQ(properties->size(), 1);
ASSERT_EQ((*properties)[property_id],
storage::PropertyValue(static_cast<int64_t>(i)));
} else {
ASSERT_EQ(properties->size(), 0);
}
}
}
// Verify label indices.
{
std::vector<storage::VertexAccessor> vertices;
vertices.reserve(kNumBaseVertices / 2);
for (auto vertex : acc.Vertices(label_unindexed, storage::View::OLD)) {
vertices.push_back(vertex);
}
ASSERT_EQ(vertices.size(), kNumBaseVertices / 2);
std::sort(vertices.begin(), vertices.end(),
[](const auto &a, const auto &b) { return a.Gid() < b.Gid(); });
for (uint64_t i = 0; i < kNumBaseVertices / 2; ++i) {
ASSERT_EQ(vertices[i].Gid(),
base_vertex_gids_[kNumBaseVertices / 2 + i]);
}
}
// Verify label+property index.
{
std::vector<storage::VertexAccessor> vertices;
vertices.reserve(kNumBaseVertices / 3);
for (auto vertex :
acc.Vertices(label_indexed, property_id, storage::View::OLD)) {
vertices.push_back(vertex);
}
ASSERT_EQ(vertices.size(), kNumBaseVertices / 3);
std::sort(vertices.begin(), vertices.end(),
[](const auto &a, const auto &b) { return a.Gid() < b.Gid(); });
for (uint64_t i = 0; i < kNumBaseVertices / 3; ++i) {
ASSERT_EQ(vertices[i].Gid(), base_vertex_gids_[i]);
}
}
}
void CreateExtendedDataset(storage::Storage *store) {
auto label_indexed = store->NameToLabel("extended_indexed");
auto label_unused = store->NameToLabel("extended_unused");
auto property_count = store->NameToProperty("count");
auto et3 = store->NameToEdgeType("extended_et3");
auto et4 = store->NameToEdgeType("extended_et4");
// Create label index.
ASSERT_TRUE(store->CreateIndex(label_unused));
// Create label+property index.
ASSERT_TRUE(store->CreateIndex(label_indexed, property_count));
// Create existence constraint.
ASSERT_FALSE(store->CreateExistenceConstraint(label_unused, property_count)
.HasError());
// Create vertices.
for (uint64_t i = 0; i < kNumExtendedVertices; ++i) {
auto acc = store->Access();
auto vertex = acc.CreateVertex();
extended_vertex_gids_[i] = vertex.Gid();
if (i < kNumExtendedVertices / 2) {
ASSERT_TRUE(vertex.AddLabel(label_indexed).HasValue());
}
if (i < kNumExtendedVertices / 3 || i >= kNumExtendedVertices / 2) {
ASSERT_TRUE(
vertex
.SetProperty(property_count, storage::PropertyValue("nandare"))
.HasValue());
}
ASSERT_FALSE(acc.Commit().HasError());
}
// Create edges.
for (uint64_t i = 0; i < kNumExtendedEdges; ++i) {
auto acc = store->Access();
auto vertex1 =
acc.FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices],
storage::View::OLD);
ASSERT_TRUE(vertex1);
auto vertex2 =
acc.FindVertex(extended_vertex_gids_[(i / 6) % kNumExtendedVertices],
storage::View::OLD);
ASSERT_TRUE(vertex2);
storage::EdgeTypeId et;
if (i < kNumExtendedEdges / 4) {
et = et3;
} else {
et = et4;
}
auto edge = acc.CreateEdge(&*vertex1, &*vertex2, et);
ASSERT_TRUE(edge.HasValue());
extended_edge_gids_[i] = edge->Gid();
ASSERT_FALSE(acc.Commit().HasError());
}
}
void VerifyExtendedDataset(storage::Storage *store) {
auto label_indexed = store->NameToLabel("extended_indexed");
auto label_unused = store->NameToLabel("extended_unused");
auto property_count = store->NameToProperty("count");
auto et3 = store->NameToEdgeType("extended_et3");
auto et4 = store->NameToEdgeType("extended_et4");
// Verify indices info.
{
auto info = store->ListAllIndices();
auto base_label_indexed = store->NameToLabel("base_indexed");
auto base_label_unindexed = store->NameToLabel("base_unindexed");
auto base_property_id = store->NameToProperty("id");
ASSERT_THAT(info.label,
UnorderedElementsAre(base_label_unindexed, label_unused));
ASSERT_THAT(info.label_property,
UnorderedElementsAre(
std::make_pair(base_label_indexed, base_property_id),
std::make_pair(label_indexed, property_count)));
}
// Verify constraints info.
{
auto info = store->ListAllConstraints();
auto base_label_unindexed = store->NameToLabel("base_unindexed");
auto base_property_id = store->NameToProperty("id");
ASSERT_THAT(info.existence,
UnorderedElementsAre(
std::make_pair(base_label_unindexed, base_property_id),
std::make_pair(label_unused, property_count)));
}
// Create storage accessor.
auto acc = store->Access();
// Verify vertices.
for (uint64_t i = 0; i < kNumExtendedVertices; ++i) {
auto vertex =
acc.FindVertex(extended_vertex_gids_[i], storage::View::OLD);
ASSERT_TRUE(vertex);
auto labels = vertex->Labels(storage::View::OLD);
ASSERT_TRUE(labels.HasValue());
if (i < kNumExtendedVertices / 2) {
ASSERT_THAT(*labels, UnorderedElementsAre(label_indexed));
}
auto properties = vertex->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
if (i < kNumExtendedVertices / 3 || i >= kNumExtendedVertices / 2) {
ASSERT_EQ(properties->size(), 1);
ASSERT_EQ((*properties)[property_count],
storage::PropertyValue("nandare"));
} else {
ASSERT_EQ(properties->size(), 0);
}
}
// Verify edges.
for (uint64_t i = 0; i < kNumExtendedEdges; ++i) {
auto find_edge =
[&](const auto &edges) -> std::optional<storage::EdgeAccessor> {
for (const auto &edge : edges) {
if (edge.Gid() == extended_edge_gids_[i]) {
return edge;
}
}
return std::nullopt;
};
{
auto vertex1 = acc.FindVertex(
extended_vertex_gids_[(i / 5) % kNumExtendedVertices],
storage::View::OLD);
ASSERT_TRUE(vertex1);
auto out_edges = vertex1->OutEdges({}, storage::View::OLD);
ASSERT_TRUE(out_edges.HasValue());
auto edge1 = find_edge(*out_edges);
ASSERT_TRUE(edge1);
if (i < kNumExtendedEdges / 4) {
ASSERT_EQ(edge1->EdgeType(), et3);
} else {
ASSERT_EQ(edge1->EdgeType(), et4);
}
auto properties = edge1->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
ASSERT_EQ(properties->size(), 0);
}
{
auto vertex2 = acc.FindVertex(
extended_vertex_gids_[(i / 6) % kNumExtendedVertices],
storage::View::OLD);
ASSERT_TRUE(vertex2);
auto in_edges = vertex2->InEdges({}, storage::View::OLD);
ASSERT_TRUE(in_edges.HasValue());
auto edge2 = find_edge(*in_edges);
ASSERT_TRUE(edge2);
if (i < kNumExtendedEdges / 4) {
ASSERT_EQ(edge2->EdgeType(), et3);
} else {
ASSERT_EQ(edge2->EdgeType(), et4);
}
auto properties = edge2->Properties(storage::View::OLD);
ASSERT_TRUE(properties.HasValue());
ASSERT_EQ(properties->size(), 0);
}
}
// Verify label indices.
{
std::vector<storage::VertexAccessor> vertices;
vertices.reserve(kNumExtendedVertices / 2);
for (auto vertex : acc.Vertices(label_unused, storage::View::OLD)) {
vertices.push_back(vertex);
}
ASSERT_EQ(vertices.size(), 0);
}
// Verify label+property index.
{
std::vector<storage::VertexAccessor> vertices;
vertices.reserve(kNumExtendedVertices / 3);
for (auto vertex :
acc.Vertices(label_indexed, property_count, storage::View::OLD)) {
vertices.push_back(vertex);
}
ASSERT_EQ(vertices.size(), kNumExtendedVertices / 3);
std::sort(vertices.begin(), vertices.end(),
[](const auto &a, const auto &b) { return a.Gid() < b.Gid(); });
for (uint64_t i = 0; i < kNumExtendedVertices / 3; ++i) {
ASSERT_EQ(vertices[i].Gid(), extended_vertex_gids_[i]);
}
}
}
std::vector<std::filesystem::path> GetSnapshotsList() {
std::vector<std::filesystem::path> ret;
for (auto &item : std::filesystem::directory_iterator(
storage_directory / storage::kSnapshotDirectory)) {
ret.push_back(item.path());
}
std::sort(ret.begin(), ret.end());
std::reverse(ret.begin(), ret.end());
return ret;
}
std::filesystem::path storage_directory{
std::filesystem::temp_directory_path() /
"MG_test_unit_storage_v2_durability"};
private:
void Clear() {
if (!std::filesystem::exists(storage_directory)) return;
std::filesystem::remove_all(storage_directory);
}
std::vector<storage::Gid> base_vertex_gids_;
std::vector<storage::Gid> base_edge_gids_;
std::vector<storage::Gid> extended_vertex_gids_;
std::vector<storage::Gid> extended_edge_gids_;
};
INSTANTIATE_TEST_CASE_P(EdgesWithProperties, DurabilityTest,
::testing::Values(true));
INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, DurabilityTest,
::testing::Values(false));
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_P(DurabilityTest, SnapshotOnExit) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.snapshot_on_exit = true}});
CreateBaseDataset(&store, GetParam());
VerifyBaseDataset(&store, GetParam(), false);
CreateExtendedDataset(&store);
VerifyBaseDataset(&store, GetParam(), true);
VerifyExtendedDataset(&store);
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, GetParam(), true);
VerifyExtendedDataset(&store);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_P(DurabilityTest, SnapshotPeriodic) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.snapshot_type =
storage::Config::Durability::SnapshotType::PERIODIC,
.snapshot_interval = std::chrono::milliseconds(2000)}});
CreateBaseDataset(&store, GetParam());
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, GetParam(), false);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_P(DurabilityTest, SnapshotFallback) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.snapshot_type =
storage::Config::Durability::SnapshotType::PERIODIC,
.snapshot_interval = std::chrono::milliseconds(2000)}});
CreateBaseDataset(&store, GetParam());
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
CreateExtendedDataset(&store);
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
}
// Destroy last snapshot.
{
auto snapshots = GetSnapshotsList();
ASSERT_GE(snapshots.size(), 2);
auto info = storage::ReadSnapshotInfo(*snapshots.begin());
LOG(INFO) << "Destroying snapshot " << *snapshots.begin();
utils::OutputFile file;
file.Open(*snapshots.begin(), utils::OutputFile::Mode::OVERWRITE_EXISTING);
file.SetPosition(utils::OutputFile::Position::SET, info.offset_vertices);
auto value = static_cast<uint8_t>(storage::Marker::TYPE_MAP);
file.Write(&value, sizeof(value));
file.Sync();
file.Close();
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, GetParam(), false);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_P(DurabilityTest, SnapshotRetention) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.snapshot_type =
storage::Config::Durability::SnapshotType::PERIODIC,
.snapshot_interval = std::chrono::milliseconds(2000),
.snapshot_retention_count = 3}});
CreateBaseDataset(&store, GetParam());
// Allow approximately 5 snapshots to be created.
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
}
// Verify that exactly 3 snapshots exist.
{
auto snapshots = GetSnapshotsList();
ASSERT_EQ(snapshots.size(), 3);
for (const auto &path : snapshots) {
// This shouldn't throw.
storage::ReadSnapshotInfo(path);
}
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = GetParam()},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, GetParam(), false);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(DurabilityTest,
SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnEdges) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = false},
.durability = {.storage_directory = storage_directory,
.snapshot_on_exit = true}});
CreateBaseDataset(&store, false);
VerifyBaseDataset(&store, false, false);
CreateExtendedDataset(&store);
VerifyBaseDataset(&store, false, true);
VerifyExtendedDataset(&store);
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = true},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, false, true);
VerifyExtendedDataset(&store);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(DurabilityTest,
SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnEdges) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = true},
.durability = {.storage_directory = storage_directory,
.snapshot_on_exit = true}});
CreateBaseDataset(&store, true);
VerifyBaseDataset(&store, true, false);
CreateExtendedDataset(&store);
VerifyBaseDataset(&store, true, true);
VerifyExtendedDataset(&store);
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = false},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
{
std::vector<storage::VertexAccessor> vertices;
auto acc = store.Access();
for (auto vertex : acc.Vertices(storage::View::OLD)) {
vertices.push_back(vertex);
}
ASSERT_EQ(vertices.size(), 0);
}
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TEST_F(DurabilityTest,
SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutPropertiesOnEdges) {
// Create snapshot.
{
storage::Storage store(
{.items = {.properties_on_edges = true},
.durability = {.storage_directory = storage_directory,
.snapshot_on_exit = true}});
CreateBaseDataset(&store, true);
VerifyBaseDataset(&store, true, false);
CreateExtendedDataset(&store);
VerifyBaseDataset(&store, true, true);
VerifyExtendedDataset(&store);
// Remove properties from edges.
{
auto acc = store.Access();
for (auto vertex : acc.Vertices(storage::View::OLD)) {
auto in_edges = vertex.InEdges({}, storage::View::OLD);
ASSERT_TRUE(in_edges.HasValue());
for (auto edge : *in_edges) {
// TODO (mferencevic): Replace with `ClearProperties()`
auto props = edge.Properties(storage::View::NEW);
ASSERT_TRUE(props.HasValue());
for (const auto &prop : *props) {
ASSERT_TRUE(edge.SetProperty(prop.first, storage::PropertyValue())
.HasValue());
}
}
auto out_edges = vertex.InEdges({}, storage::View::OLD);
ASSERT_TRUE(out_edges.HasValue());
for (auto edge : *out_edges) {
// TODO (mferencevic): Replace with `ClearProperties()`
auto props = edge.Properties(storage::View::NEW);
ASSERT_TRUE(props.HasValue());
for (const auto &prop : *props) {
ASSERT_TRUE(edge.SetProperty(prop.first, storage::PropertyValue())
.HasValue());
}
}
}
ASSERT_FALSE(acc.Commit().HasError());
}
}
// Recover snapshot.
storage::Storage store({.items = {.properties_on_edges = false},
.durability = {.storage_directory = storage_directory,
.recover_on_startup = true}});
VerifyBaseDataset(&store, false, true);
VerifyExtendedDataset(&store);
// Try to use the storage.
{
auto acc = store.Access();
auto vertex = acc.CreateVertex();
auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et"));
ASSERT_TRUE(edge.HasValue());
ASSERT_FALSE(acc.Commit().HasError());
}
}