// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #include #include #include #include #include #include "storage/v2/durability/exceptions.hpp" #include "storage/v2/durability/version.hpp" #include "storage/v2/durability/wal.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/name_id_mapper.hpp" #include "utils/file.hpp" #include "utils/file_locker.hpp" #include "utils/uuid.hpp" // Helper function used to convert between enum types. memgraph::storage::durability::WalDeltaData::Type StorageGlobalOperationToWalDeltaDataType( memgraph::storage::durability::StorageGlobalOperation operation) { switch (operation) { case memgraph::storage::durability::StorageGlobalOperation::LABEL_INDEX_CREATE: return memgraph::storage::durability::WalDeltaData::Type::LABEL_INDEX_CREATE; case memgraph::storage::durability::StorageGlobalOperation::LABEL_INDEX_DROP: return memgraph::storage::durability::WalDeltaData::Type::LABEL_INDEX_DROP; case memgraph::storage::durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: return memgraph::storage::durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE; case memgraph::storage::durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: return memgraph::storage::durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP; case memgraph::storage::durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: return memgraph::storage::durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE; case memgraph::storage::durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: return memgraph::storage::durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP; case memgraph::storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: return memgraph::storage::durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE; case memgraph::storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: return memgraph::storage::durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP; } } // This class mimics the internals of the storage to generate the deltas. class DeltaGenerator final { public: class Transaction final { private: friend class DeltaGenerator; explicit Transaction(DeltaGenerator *gen) : gen_(gen), transaction_(gen->transaction_id_++, gen->timestamp_++, memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION) {} public: memgraph::storage::Vertex *CreateVertex() { auto gid = memgraph::storage::Gid::FromUint(gen_->vertices_count_++); auto delta = memgraph::storage::CreateDeleteObjectDelta(&transaction_); auto &it = gen_->vertices_.emplace_back(gid, delta); delta->prev.Set(&it); { memgraph::storage::durability::WalDeltaData data; data.type = memgraph::storage::durability::WalDeltaData::Type::VERTEX_CREATE; data.vertex_create_delete.gid = gid; data_.push_back(data); } return ⁢ } void DeleteVertex(memgraph::storage::Vertex *vertex) { memgraph::storage::CreateAndLinkDelta(&transaction_, &*vertex, memgraph::storage::Delta::RecreateObjectTag()); { memgraph::storage::durability::WalDeltaData data; data.type = memgraph::storage::durability::WalDeltaData::Type::VERTEX_DELETE; data.vertex_create_delete.gid = vertex->gid; data_.push_back(data); } } void AddLabel(memgraph::storage::Vertex *vertex, const std::string &label) { auto label_id = memgraph::storage::LabelId::FromUint(gen_->mapper_.NameToId(label)); vertex->labels.push_back(label_id); memgraph::storage::CreateAndLinkDelta(&transaction_, &*vertex, memgraph::storage::Delta::RemoveLabelTag(), label_id); { memgraph::storage::durability::WalDeltaData data; data.type = memgraph::storage::durability::WalDeltaData::Type::VERTEX_ADD_LABEL; data.vertex_add_remove_label.gid = vertex->gid; data.vertex_add_remove_label.label = label; data_.push_back(data); } } void RemoveLabel(memgraph::storage::Vertex *vertex, const std::string &label) { auto label_id = memgraph::storage::LabelId::FromUint(gen_->mapper_.NameToId(label)); vertex->labels.erase(std::find(vertex->labels.begin(), vertex->labels.end(), label_id)); memgraph::storage::CreateAndLinkDelta(&transaction_, &*vertex, memgraph::storage::Delta::AddLabelTag(), label_id); { memgraph::storage::durability::WalDeltaData data; data.type = memgraph::storage::durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL; data.vertex_add_remove_label.gid = vertex->gid; data.vertex_add_remove_label.label = label; data_.push_back(data); } } void SetProperty(memgraph::storage::Vertex *vertex, const std::string &property, const memgraph::storage::PropertyValue &value) { auto property_id = memgraph::storage::PropertyId::FromUint(gen_->mapper_.NameToId(property)); auto &props = vertex->properties; auto old_value = props.GetProperty(property_id); memgraph::storage::CreateAndLinkDelta(&transaction_, &*vertex, memgraph::storage::Delta::SetPropertyTag(), property_id, old_value); props.SetProperty(property_id, value); { memgraph::storage::durability::WalDeltaData data; data.type = memgraph::storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY; data.vertex_edge_set_property.gid = vertex->gid; data.vertex_edge_set_property.property = property; // We don't store the property value here. That is because the storage // generates multiple `SetProperty` deltas using only the final values // of the property. The intermediate values aren't encoded. The value is // later determined in the `Finalize` function. data_.push_back(data); } } void Finalize(bool append_transaction_end = true) { auto commit_timestamp = gen_->timestamp_++; for (const auto &delta : transaction_.deltas) { auto owner = delta.prev.Get(); while (owner.type == memgraph::storage::PreviousPtr::Type::DELTA) { owner = owner.delta->prev.Get(); } if (owner.type == memgraph::storage::PreviousPtr::Type::VERTEX) { gen_->wal_file_.AppendDelta(delta, *owner.vertex, commit_timestamp); } else if (owner.type == memgraph::storage::PreviousPtr::Type::EDGE) { gen_->wal_file_.AppendDelta(delta, *owner.edge, commit_timestamp); } else { LOG_FATAL("Invalid delta owner!"); } } if (append_transaction_end) { gen_->wal_file_.AppendTransactionEnd(commit_timestamp); if (gen_->valid_) { gen_->UpdateStats(commit_timestamp, transaction_.deltas.size() + 1); for (auto &data : data_) { if (data.type == memgraph::storage::durability::WalDeltaData::Type::VERTEX_SET_PROPERTY) { // We need to put the final property value into the SET_PROPERTY // delta. auto vertex = std::find(gen_->vertices_.begin(), gen_->vertices_.end(), data.vertex_edge_set_property.gid); ASSERT_NE(vertex, gen_->vertices_.end()); auto property_id = memgraph::storage::PropertyId::FromUint( gen_->mapper_.NameToId(data.vertex_edge_set_property.property)); data.vertex_edge_set_property.value = vertex->properties.GetProperty(property_id); } gen_->data_.emplace_back(commit_timestamp, data); } memgraph::storage::durability::WalDeltaData data{ .type = memgraph::storage::durability::WalDeltaData::Type::TRANSACTION_END}; gen_->data_.emplace_back(commit_timestamp, data); } } else { gen_->valid_ = false; } } private: DeltaGenerator *gen_; memgraph::storage::Transaction transaction_; std::vector data_; }; using DataT = std::vector>; DeltaGenerator(const std::filesystem::path &data_directory, bool properties_on_edges, uint64_t seq_num) : uuid_(memgraph::utils::GenerateUUID()), epoch_id_(memgraph::utils::GenerateUUID()), seq_num_(seq_num), wal_file_(data_directory, uuid_, epoch_id_, {.properties_on_edges = properties_on_edges}, &mapper_, seq_num, &file_retainer_) {} Transaction CreateTransaction() { return Transaction(this); } void ResetTransactionIds() { transaction_id_ = memgraph::storage::kTransactionInitialId; timestamp_ = memgraph::storage::kTimestampInitialId; valid_ = false; } void AppendOperation(memgraph::storage::durability::StorageGlobalOperation operation, const std::string &label, const std::set properties = {}) { auto label_id = memgraph::storage::LabelId::FromUint(mapper_.NameToId(label)); std::set property_ids; for (const auto &property : properties) { property_ids.insert(memgraph::storage::PropertyId::FromUint(mapper_.NameToId(property))); } wal_file_.AppendOperation(operation, label_id, property_ids, timestamp_); if (valid_) { UpdateStats(timestamp_, 1); memgraph::storage::durability::WalDeltaData data; data.type = StorageGlobalOperationToWalDeltaDataType(operation); switch (operation) { case memgraph::storage::durability::StorageGlobalOperation::LABEL_INDEX_CREATE: case memgraph::storage::durability::StorageGlobalOperation::LABEL_INDEX_DROP: data.operation_label.label = label; break; case memgraph::storage::durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE: case memgraph::storage::durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP: case memgraph::storage::durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE: case memgraph::storage::durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: data.operation_label_property.label = label; data.operation_label_property.property = *properties.begin(); case memgraph::storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE: case memgraph::storage::durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: data.operation_label_properties.label = label; data.operation_label_properties.properties = properties; } data_.emplace_back(timestamp_, data); } } uint64_t GetPosition() { return wal_file_.GetSize(); } memgraph::storage::durability::WalInfo GetInfo() { return {.offset_metadata = 0, .offset_deltas = 0, .uuid = uuid_, .epoch_id = epoch_id_, .seq_num = seq_num_, .from_timestamp = tx_from_, .to_timestamp = tx_to_, .num_deltas = deltas_count_}; } DataT GetData() { return data_; } private: void UpdateStats(uint64_t timestamp, uint64_t count) { if (deltas_count_ == 0) { tx_from_ = timestamp; } tx_to_ = timestamp; deltas_count_ += count; } std::string uuid_; std::string epoch_id_; uint64_t seq_num_; uint64_t transaction_id_{memgraph::storage::kTransactionInitialId}; uint64_t timestamp_{memgraph::storage::kTimestampInitialId}; uint64_t vertices_count_{0}; std::list vertices_; memgraph::storage::NameIdMapper mapper_; memgraph::storage::durability::WalFile wal_file_; DataT data_; uint64_t deltas_count_{0}; uint64_t tx_from_{0}; uint64_t tx_to_{0}; uint64_t valid_{true}; memgraph::utils::FileRetainer file_retainer_; }; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define TRANSACTION(append_transaction_end, ops) \ { \ auto tx = gen.CreateTransaction(); \ ops; \ tx.Finalize(append_transaction_end); \ } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define OPERATION(op, ...) gen.AppendOperation(memgraph::storage::durability::StorageGlobalOperation::op, __VA_ARGS__) void AssertWalInfoEqual(const memgraph::storage::durability::WalInfo &a, const memgraph::storage::durability::WalInfo &b) { ASSERT_EQ(a.uuid, b.uuid); ASSERT_EQ(a.epoch_id, b.epoch_id); ASSERT_EQ(a.seq_num, b.seq_num); ASSERT_EQ(a.from_timestamp, b.from_timestamp); ASSERT_EQ(a.to_timestamp, b.to_timestamp); ASSERT_EQ(a.num_deltas, b.num_deltas); } void AssertWalDataEqual(const DeltaGenerator::DataT &data, const std::filesystem::path &path) { auto info = memgraph::storage::durability::ReadWalInfo(path); memgraph::storage::durability::Decoder wal; wal.Initialize(path, memgraph::storage::durability::kWalMagic); wal.SetPosition(info.offset_deltas); DeltaGenerator::DataT current; for (uint64_t i = 0; i < info.num_deltas; ++i) { auto timestamp = memgraph::storage::durability::ReadWalDeltaHeader(&wal); current.emplace_back(timestamp, memgraph::storage::durability::ReadWalDeltaData(&wal)); } ASSERT_EQ(data.size(), current.size()); ASSERT_EQ(data, current); } class WalFileTest : public ::testing::TestWithParam { public: WalFileTest() {} void SetUp() override { Clear(); } void TearDown() override { Clear(); } std::vector GetFilesList() { std::vector ret; for (auto &item : std::filesystem::directory_iterator(storage_directory)) { 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_wal_file"}; private: void Clear() { if (!std::filesystem::exists(storage_directory)) return; std::filesystem::remove_all(storage_directory); } }; INSTANTIATE_TEST_CASE_P(EdgesWithProperties, WalFileTest, ::testing::Values(true)); INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, WalFileTest, ::testing::Values(false)); // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(WalFileTest, EmptyFile) { { DeltaGenerator gen(storage_directory, GetParam(), 5); } auto wal_files = GetFilesList(); ASSERT_EQ(wal_files.size(), 0); } // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GENERATE_SIMPLE_TEST(name, ops) \ TEST_P(WalFileTest, name) { \ memgraph::storage::durability::WalInfo info; \ DeltaGenerator::DataT data; \ \ { \ DeltaGenerator gen(storage_directory, GetParam(), 5); \ ops; \ info = gen.GetInfo(); \ data = gen.GetData(); \ } \ \ auto wal_files = GetFilesList(); \ ASSERT_EQ(wal_files.size(), 1); \ \ if (info.num_deltas == 0) { \ ASSERT_THROW(memgraph::storage::durability::ReadWalInfo(wal_files.front()), \ memgraph::storage::durability::RecoveryFailure); \ } else { \ AssertWalInfoEqual(info, memgraph::storage::durability::ReadWalInfo(wal_files.front())); \ AssertWalDataEqual(data, wal_files.front()); \ } \ } // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionWithEnd, { TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionWithoutEnd, { TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(OperationSingle, { OPERATION(LABEL_INDEX_CREATE, "hello"); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsEnd00, { TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsEnd01, { TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsEnd10, { TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsEnd11, { TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation_00, { OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation_01, { OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation_10, { OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation_11, { OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation0_0, { TRANSACTION(false, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation0_1, { TRANSACTION(false, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation1_0, { TRANSACTION(true, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(false, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation1_1, { TRANSACTION(true, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation00_, { TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation01_, { TRANSACTION(false, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation10_, { TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(false, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(TransactionsWithOperation11_, { TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); OPERATION(LABEL_INDEX_CREATE, "hello"); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(AllTransactionOperationsWithEnd, { TRANSACTION(true, { auto vertex1 = tx.CreateVertex(); auto vertex2 = tx.CreateVertex(); tx.AddLabel(vertex1, "test"); tx.AddLabel(vertex2, "hello"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue("nandare")); tx.RemoveLabel(vertex1, "test"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue(123)); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue()); tx.DeleteVertex(vertex1); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(AllTransactionOperationsWithoutEnd, { TRANSACTION(false, { auto vertex1 = tx.CreateVertex(); auto vertex2 = tx.CreateVertex(); tx.AddLabel(vertex1, "test"); tx.AddLabel(vertex2, "hello"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue("nandare")); tx.RemoveLabel(vertex1, "test"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue(123)); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue()); tx.DeleteVertex(vertex1); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(AllGlobalOperations, { OPERATION(LABEL_INDEX_CREATE, "hello"); OPERATION(LABEL_INDEX_DROP, "hello"); OPERATION(LABEL_PROPERTY_INDEX_CREATE, "hello", {"world"}); OPERATION(LABEL_PROPERTY_INDEX_DROP, "hello", {"world"}); OPERATION(EXISTENCE_CONSTRAINT_CREATE, "hello", {"world"}); OPERATION(EXISTENCE_CONSTRAINT_DROP, "hello", {"world"}); OPERATION(UNIQUE_CONSTRAINT_CREATE, "hello", {"world", "and", "universe"}); OPERATION(UNIQUE_CONSTRAINT_DROP, "hello", {"world", "and", "universe"}); }); // NOLINTNEXTLINE(hicpp-special-member-functions) GENERATE_SIMPLE_TEST(InvalidTransactionOrdering, { TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); TRANSACTION(true, { tx.CreateVertex(); }); gen.ResetTransactionIds(); TRANSACTION(true, { tx.CreateVertex(); }); }); // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(WalFileTest, InvalidMarker) { memgraph::storage::durability::WalInfo info; { DeltaGenerator gen(storage_directory, GetParam(), 5); TRANSACTION(true, { tx.CreateVertex(); }); info = gen.GetInfo(); } auto wal_files = GetFilesList(); ASSERT_EQ(wal_files.size(), 1); const auto &wal_file = wal_files.front(); auto final_info = memgraph::storage::durability::ReadWalInfo(wal_file); AssertWalInfoEqual(info, final_info); size_t i = 0; for (auto marker : memgraph::storage::durability::kMarkersAll) { if (marker == memgraph::storage::durability::Marker::SECTION_DELTA) continue; auto current_file = storage_directory / fmt::format("temporary_{}", i); ASSERT_TRUE(std::filesystem::copy_file(wal_file, current_file)); memgraph::utils::OutputFile file; file.Open(current_file, memgraph::utils::OutputFile::Mode::OVERWRITE_EXISTING); file.SetPosition(memgraph::utils::OutputFile::Position::SET, final_info.offset_deltas); auto value = static_cast(marker); file.Write(&value, sizeof(value)); file.Sync(); file.Close(); ASSERT_THROW(memgraph::storage::durability::ReadWalInfo(current_file), memgraph::storage::durability::RecoveryFailure); ++i; } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(WalFileTest, PartialData) { std::vector> infos; { DeltaGenerator gen(storage_directory, GetParam(), 5); TRANSACTION(true, { tx.CreateVertex(); }); infos.emplace_back(gen.GetPosition(), gen.GetInfo()); TRANSACTION(true, { auto vertex = tx.CreateVertex(); tx.AddLabel(vertex, "hello"); }); infos.emplace_back(gen.GetPosition(), gen.GetInfo()); OPERATION(LABEL_PROPERTY_INDEX_CREATE, "hello", {"world"}); infos.emplace_back(gen.GetPosition(), gen.GetInfo()); TRANSACTION(true, { auto vertex1 = tx.CreateVertex(); auto vertex2 = tx.CreateVertex(); tx.AddLabel(vertex1, "test"); tx.AddLabel(vertex2, "hello"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue("nandare")); tx.RemoveLabel(vertex1, "test"); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue(123)); tx.SetProperty(vertex2, "hello", memgraph::storage::PropertyValue()); tx.DeleteVertex(vertex1); }); infos.emplace_back(gen.GetPosition(), gen.GetInfo()); } auto wal_files = GetFilesList(); ASSERT_EQ(wal_files.size(), 1); const auto &wal_file = wal_files.front(); AssertWalInfoEqual(infos.back().second, memgraph::storage::durability::ReadWalInfo(wal_file)); auto current_file = storage_directory / "temporary"; memgraph::utils::InputFile infile; infile.Open(wal_file); uint64_t pos = 0; for (size_t i = 0; i < infile.GetSize(); ++i) { if (i < infos.front().first) { ASSERT_THROW(memgraph::storage::durability::ReadWalInfo(current_file), memgraph::storage::durability::RecoveryFailure); } else { if (i >= infos[pos + 1].first) ++pos; AssertWalInfoEqual(infos[pos].second, memgraph::storage::durability::ReadWalInfo(current_file)); } { memgraph::utils::OutputFile outfile; outfile.Open(current_file, memgraph::utils::OutputFile::Mode::APPEND_TO_EXISTING); uint8_t value; ASSERT_TRUE(infile.Read(&value, sizeof(value))); outfile.Write(&value, sizeof(value)); outfile.Sync(); outfile.Close(); } } ASSERT_EQ(pos, infos.size() - 2); AssertWalInfoEqual(infos[infos.size() - 1].second, memgraph::storage::durability::ReadWalInfo(current_file)); }