From a0705746cb545d6ea529945aa80d7a423f751c3c Mon Sep 17 00:00:00 2001 From: antonio2368 Date: Tue, 1 Dec 2020 16:51:25 +0100 Subject: [PATCH] Add epoch id and refactor replication client/server (#51) --- CHANGELOG.md | 9 + src/storage/v2/CMakeLists.txt | 3 +- src/storage/v2/durability/durability.cpp | 74 +- src/storage/v2/durability/durability.hpp | 6 +- src/storage/v2/durability/marker.hpp | 2 + src/storage/v2/durability/serialization.cpp | 2 + src/storage/v2/durability/snapshot.cpp | 78 +- src/storage/v2/durability/snapshot.hpp | 32 +- src/storage/v2/durability/version.hpp | 4 +- src/storage/v2/durability/wal.cpp | 45 +- src/storage/v2/durability/wal.hpp | 39 +- src/storage/v2/replication/enums.hpp | 13 + ...replication.cpp => replication_client.cpp} | 225 +++--- ...replication.hpp => replication_client.hpp} | 42 +- .../v2/replication/replication_server.cpp | 727 ++++++++++++++++++ .../v2/replication/replication_server.hpp | 39 + src/storage/v2/replication/rpc.lcp | 5 +- src/storage/v2/storage.cpp | 709 ++--------------- src/storage/v2/storage.hpp | 74 +- tests/integration/durability/runner.py | 65 +- .../tests/v12/test_all/expected.cypher | 11 - .../tests/v12/test_all/snapshot.bin | Bin 946 -> 0 bytes .../durability/tests/v12/test_all/wal.bin | Bin 1023 -> 0 bytes .../v12/test_constraints/expected.cypher | 10 - .../tests/v12/test_constraints/snapshot.bin | Bin 642 -> 0 bytes .../tests/v12/test_constraints/wal.bin | Bin 1437 -> 0 bytes .../tests/v12/test_edges/expected.cypher | 45 -- .../tests/v12/test_edges/snapshot.bin | Bin 3123 -> 0 bytes .../durability/tests/v12/test_edges/wal.bin | Bin 5226 -> 0 bytes .../expected.cypher | 24 - .../test_edges_with_properties/snapshot.bin | Bin 1606 -> 0 bytes .../v12/test_edges_with_properties/wal.bin | Bin 1111 -> 0 bytes .../tests/v12/test_indices/expected.cypher | 16 - .../tests/v12/test_indices/snapshot.bin | Bin 898 -> 0 bytes .../durability/tests/v12/test_indices/wal.bin | Bin 2425 -> 0 bytes .../tests/v12/test_vertices/snapshot.bin | Bin 1575 -> 0 bytes .../tests/v12/test_vertices/wal.bin | Bin 1778 -> 0 bytes .../tests/v13/test_all/snapshot.bin | Bin 1467 -> 0 bytes .../durability/tests/v13/test_all/wal.bin | Bin 1731 -> 0 bytes .../tests/v13/test_constraints/snapshot.bin | Bin 488 -> 0 bytes .../tests/v13/test_constraints/wal.bin | Bin 349 -> 0 bytes .../tests/v13/test_edges/snapshot.bin | Bin 4124 -> 0 bytes .../durability/tests/v13/test_edges/wal.bin | Bin 3024 -> 0 bytes .../tests/v13/test_indices/snapshot.bin | Bin 343 -> 0 bytes .../durability/tests/v13/test_indices/wal.bin | Bin 357 -> 0 bytes .../tests/v13/test_vertices/snapshot.bin | Bin 1584 -> 0 bytes .../tests/v13/test_vertices/wal.bin | Bin 1778 -> 0 bytes .../v14/test_all/expected_snapshot.cypher | 16 + .../test_all/expected_wal.cypher} | 0 .../tests/v14/test_all/snapshot.bin | Bin 0 -> 1531 bytes .../durability/tests/v14/test_all/wal.bin | Bin 0 -> 2610 bytes .../test_constraints/expected_snapshot.cypher | 6 + .../test_constraints/expected_wal.cypher} | 0 .../tests/v14/test_constraints/snapshot.bin | Bin 0 -> 552 bytes .../tests/v14/test_constraints/wal.bin | Bin 0 -> 394 bytes .../test_edges/expected_snapshot.cypher} | 0 .../tests/v14/test_edges/expected_wal.cypher | 58 ++ .../tests/v14/test_edges/snapshot.bin | Bin 0 -> 4188 bytes .../durability/tests/v14/test_edges/wal.bin | Bin 0 -> 6429 bytes .../test_indices/expected_snapshot.cypher} | 0 .../v14/test_indices/expected_wal.cypher | 4 + .../tests/v14/test_indices/snapshot.bin | Bin 0 -> 407 bytes .../durability/tests/v14/test_indices/wal.bin | Bin 0 -> 274 bytes .../test_vertices/expected_snapshot.cypher} | 6 +- .../test_vertices/expected_wal.cypher} | 2 +- .../tests/v14/test_vertices/snapshot.bin | Bin 0 -> 1648 bytes .../tests/v14/test_vertices/wal.bin | Bin 0 -> 4172 bytes tests/integration/mg_import_csv/runner.py | 44 +- .../tests/array_types/expected.cypher | 2 +- .../expected.cypher | 2 +- .../tests/bad_relationships/expected.cypher | 2 +- .../tests/csv_parser_test1/expected.cypher | 2 +- .../tests/csv_parser_test10/expected.cypher | 2 +- .../tests/csv_parser_test12/expected.cypher | 2 +- .../tests/csv_parser_test13/expected.cypher | 2 +- .../tests/csv_parser_test15/expected.cypher | 2 +- .../tests/csv_parser_test16/expected.cypher | 2 +- .../tests/csv_parser_test17/expected.cypher | 2 +- .../tests/csv_parser_test18/expected.cypher | 2 +- .../tests/csv_parser_test2/expected.cypher | 2 +- .../tests/csv_parser_test20/expected.cypher | 2 +- .../tests/csv_parser_test21/expected.cypher | 2 +- .../tests/csv_parser_test23/expected.cypher | 2 +- .../tests/csv_parser_test3/expected.cypher | 2 +- .../tests/csv_parser_test5/expected.cypher | 2 +- .../tests/csv_parser_test6/expected.cypher | 2 +- .../tests/csv_parser_test8/expected.cypher | 2 +- .../tests/csv_parser_test9/expected.cypher | 2 +- .../expected.cypher | 2 +- .../tests/duplicate_nodes/expected.cypher | 2 +- .../tests/extra_columns_nodes/expected.cypher | 2 +- .../expected.cypher | 2 +- .../tests/field_types/expected.cypher | 2 +- .../tests/id_type/expected_integer.cypher | 2 +- .../tests/id_type/expected_string.cypher | 2 +- .../expected_disabled.cypher | 2 +- .../expected_enabled.cypher | 2 +- .../tests/ignored_columns/expected.cypher | 2 +- .../expected.cypher | 2 +- .../tests/neo_example/expected.cypher | 2 +- .../tests/node_id/expected.cypher | 2 +- .../relationship_properties/expected.cypher | 2 +- .../expected.cypher | 2 +- .../trim_strings/expected_disabled.cypher | 2 +- .../trim_strings/expected_enabled.cypher | 2 +- tests/unit/storage_v2_decoder_encoder.cpp | 1 + tests/unit/storage_v2_replication.cpp | 119 ++- tests/unit/storage_v2_wal_file.cpp | 6 +- 108 files changed, 1535 insertions(+), 1100 deletions(-) create mode 100644 src/storage/v2/replication/enums.hpp rename src/storage/v2/replication/{replication.cpp => replication_client.cpp} (67%) rename src/storage/v2/replication/{replication.hpp => replication_client.hpp} (78%) create mode 100644 src/storage/v2/replication/replication_server.cpp create mode 100644 src/storage/v2/replication/replication_server.hpp delete mode 100644 tests/integration/durability/tests/v12/test_all/expected.cypher delete mode 100644 tests/integration/durability/tests/v12/test_all/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_all/wal.bin delete mode 100644 tests/integration/durability/tests/v12/test_constraints/expected.cypher delete mode 100644 tests/integration/durability/tests/v12/test_constraints/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_constraints/wal.bin delete mode 100644 tests/integration/durability/tests/v12/test_edges/expected.cypher delete mode 100644 tests/integration/durability/tests/v12/test_edges/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_edges/wal.bin delete mode 100644 tests/integration/durability/tests/v12/test_edges_with_properties/expected.cypher delete mode 100644 tests/integration/durability/tests/v12/test_edges_with_properties/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_edges_with_properties/wal.bin delete mode 100644 tests/integration/durability/tests/v12/test_indices/expected.cypher delete mode 100644 tests/integration/durability/tests/v12/test_indices/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_indices/wal.bin delete mode 100644 tests/integration/durability/tests/v12/test_vertices/snapshot.bin delete mode 100644 tests/integration/durability/tests/v12/test_vertices/wal.bin delete mode 100644 tests/integration/durability/tests/v13/test_all/snapshot.bin delete mode 100644 tests/integration/durability/tests/v13/test_all/wal.bin delete mode 100644 tests/integration/durability/tests/v13/test_constraints/snapshot.bin delete mode 100644 tests/integration/durability/tests/v13/test_constraints/wal.bin delete mode 100644 tests/integration/durability/tests/v13/test_edges/snapshot.bin delete mode 100644 tests/integration/durability/tests/v13/test_edges/wal.bin delete mode 100644 tests/integration/durability/tests/v13/test_indices/snapshot.bin delete mode 100644 tests/integration/durability/tests/v13/test_indices/wal.bin delete mode 100644 tests/integration/durability/tests/v13/test_vertices/snapshot.bin delete mode 100644 tests/integration/durability/tests/v13/test_vertices/wal.bin create mode 100644 tests/integration/durability/tests/v14/test_all/expected_snapshot.cypher rename tests/integration/durability/tests/{v13/test_all/expected.cypher => v14/test_all/expected_wal.cypher} (100%) create mode 100644 tests/integration/durability/tests/v14/test_all/snapshot.bin create mode 100644 tests/integration/durability/tests/v14/test_all/wal.bin create mode 100644 tests/integration/durability/tests/v14/test_constraints/expected_snapshot.cypher rename tests/integration/durability/tests/{v13/test_constraints/expected.cypher => v14/test_constraints/expected_wal.cypher} (100%) create mode 100644 tests/integration/durability/tests/v14/test_constraints/snapshot.bin create mode 100644 tests/integration/durability/tests/v14/test_constraints/wal.bin rename tests/integration/durability/tests/{v13/test_edges/expected.cypher => v14/test_edges/expected_snapshot.cypher} (100%) create mode 100644 tests/integration/durability/tests/v14/test_edges/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v14/test_edges/snapshot.bin create mode 100644 tests/integration/durability/tests/v14/test_edges/wal.bin rename tests/integration/durability/tests/{v13/test_indices/expected.cypher => v14/test_indices/expected_snapshot.cypher} (100%) create mode 100644 tests/integration/durability/tests/v14/test_indices/expected_wal.cypher create mode 100644 tests/integration/durability/tests/v14/test_indices/snapshot.bin create mode 100644 tests/integration/durability/tests/v14/test_indices/wal.bin rename tests/integration/durability/tests/{v12/test_vertices/expected.cypher => v14/test_vertices/expected_snapshot.cypher} (78%) rename tests/integration/durability/tests/{v13/test_vertices/expected.cypher => v14/test_vertices/expected_wal.cypher} (94%) create mode 100644 tests/integration/durability/tests/v14/test_vertices/snapshot.bin create mode 100644 tests/integration/durability/tests/v14/test_vertices/wal.bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 407a2a2bd..1263a3eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## Future + +### Breaking Changes + +* Added extra information in durability files to support replication making + it incompatible with the durability files generated by older versions of + Memgraph. Even though the replication is an Enterprise feature, the files + are compatible with the Community version. + ## v1.2.0 ### Breaking Changes diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index 641dc5607..3d8206475 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -19,7 +19,8 @@ if(MG_ENTERPRISE) set(storage_v2_src_files ${storage_v2_src_files} - replication/replication.cpp + replication/replication_client.cpp + replication/replication_server.cpp replication/serialization.cpp replication/slk.cpp ${lcp_storage_cpp_files}) diff --git a/src/storage/v2/durability/durability.cpp b/src/storage/v2/durability/durability.cpp index fd7427308..9dbb051a7 100644 --- a/src/storage/v2/durability/durability.cpp +++ b/src/storage/v2/durability/durability.cpp @@ -92,7 +92,7 @@ std::optional> GetWalFiles( (!current_seq_num || info.seq_num < current_seq_num)) wal_files.emplace_back(info.seq_num, info.from_timestamp, info.to_timestamp, std::move(info.uuid), - item.path()); + std::move(info.epoch_id), item.path()); } catch (const RecoveryFailure &e) { DLOG(WARNING) << "Failed to read " << item.path(); continue; @@ -146,6 +146,8 @@ void RecoverIndicesAndConstraints( std::optional RecoverData( const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, + std::deque> *epoch_history, utils::SkipList *vertices, utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, @@ -174,8 +176,8 @@ std::optional RecoverData( } LOG(INFO) << "Starting snapshot recovery from " << path; try { - recovered_snapshot = LoadSnapshot(path, vertices, edges, name_id_mapper, - edge_count, items); + recovered_snapshot = LoadSnapshot(path, vertices, edges, epoch_history, + name_id_mapper, edge_count, items); LOG(INFO) << "Snapshot recovery successful!"; break; } catch (const RecoveryFailure &e) { @@ -191,6 +193,8 @@ std::optional RecoverData( recovery_info = recovered_snapshot->recovery_info; indices_constraints = std::move(recovered_snapshot->indices_constraints); snapshot_timestamp = recovered_snapshot->snapshot_info.start_timestamp; + *epoch_id = std::move(recovered_snapshot->snapshot_info.epoch_id); + if (!utils::DirExists(wal_directory)) { RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices); @@ -199,14 +203,29 @@ std::optional RecoverData( } else { std::error_code error_code; if (!utils::DirExists(wal_directory)) return std::nullopt; - // Array of all discovered WAL files, ordered by name. - std::vector> wal_files; + // We use this smaller struct that contains only a subset of information + // necessary for the rest of the recovery function. + // Also, the struct is sorted primarily on the path it contains. + struct WalFileInfo { + explicit WalFileInfo(std::filesystem::path path, std::string uuid, + std::string epoch_id) + : path(std::move(path)), + uuid(std::move(uuid)), + epoch_id(std::move(epoch_id)) {} + std::filesystem::path path; + std::string uuid; + std::string epoch_id; + + auto operator<=>(const WalFileInfo &) const = default; + }; + std::vector wal_files; for (const auto &item : std::filesystem::directory_iterator(wal_directory, error_code)) { if (!item.is_regular_file()) continue; try { auto info = ReadWalInfo(item.path()); - wal_files.emplace_back(item.path(), info.uuid); + wal_files.emplace_back(item.path(), std::move(info.uuid), + std::move(info.epoch_id)); } catch (const RecoveryFailure &e) { continue; } @@ -216,7 +235,9 @@ std::optional RecoverData( if (wal_files.empty()) return std::nullopt; std::sort(wal_files.begin(), wal_files.end()); // UUID used for durability is the UUID of the last WAL file. - *uuid = wal_files.back().second; + // Same for the epoch id. + *uuid = std::move(wal_files.back().uuid); + *epoch_id = std::move(wal_files.back().epoch_id); } auto maybe_wal_files = GetWalFiles(wal_directory, *uuid); @@ -238,9 +259,8 @@ std::optional RecoverData( if (!wal_files.empty()) { { - const auto &[seq_num, from_timestamp, to_timestamp, _, path] = - wal_files[0]; - if (seq_num != 0) { + const auto &first_wal = wal_files[0]; + if (first_wal.seq_num != 0) { // We don't have all WAL files. We need to see whether we need them all. if (!snapshot_timestamp) { // We didn't recover from a snapshot and we must have all WAL files @@ -248,7 +268,7 @@ std::optional RecoverData( // data from them. LOG(FATAL) << "There are missing prefix WAL files and data can't be " "recovered without them!"; - } else if (to_timestamp >= *snapshot_timestamp) { + } else if (first_wal.to_timestamp >= *snapshot_timestamp) { // We recovered from a snapshot and we must have at least one WAL file // whose all deltas were created before the snapshot in order to // verify that nothing is missing from the beginning of the WAL chain. @@ -259,28 +279,42 @@ std::optional RecoverData( } std::optional previous_seq_num; auto last_loaded_timestamp = snapshot_timestamp; - for (const auto &[seq_num, from_timestamp, to_timestamp, _, path] : - wal_files) { - if (previous_seq_num && *previous_seq_num + 1 != seq_num && - *previous_seq_num != seq_num) { + for (auto &wal_file : wal_files) { + if (previous_seq_num && (wal_file.seq_num - *previous_seq_num) > 1) { LOG(FATAL) << "You are missing a WAL file with the sequence number " << *previous_seq_num + 1 << "!"; } - previous_seq_num = seq_num; + previous_seq_num = wal_file.seq_num; + + if (wal_file.epoch_id != *epoch_id) { + // This way we skip WALs finalized only because of role change. + // We can also set the last timestamp to 0 if last loaded timestamp + // is nullopt as this can only happen if the WAL file with seq = 0 + // does not contain any deltas and we didn't find any snapshots. + if (last_loaded_timestamp) { + epoch_history->emplace_back(wal_file.epoch_id, + *last_loaded_timestamp); + } + *epoch_id = std::move(wal_file.epoch_id); + } try { - auto info = LoadWal(path, &indices_constraints, last_loaded_timestamp, - vertices, edges, name_id_mapper, edge_count, items); + auto info = + LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, + vertices, edges, name_id_mapper, edge_count, items); recovery_info.next_vertex_id = std::max(recovery_info.next_vertex_id, info.next_vertex_id); recovery_info.next_edge_id = std::max(recovery_info.next_edge_id, info.next_edge_id); recovery_info.next_timestamp = std::max(recovery_info.next_timestamp, info.next_timestamp); - last_loaded_timestamp.emplace(recovery_info.next_timestamp - 1); } catch (const RecoveryFailure &e) { - LOG(FATAL) << "Couldn't recover WAL deltas from " << path + LOG(FATAL) << "Couldn't recover WAL deltas from " << wal_file.path << " because of: " << e.what(); } + + if (recovery_info.next_timestamp != 0) { + last_loaded_timestamp.emplace(recovery_info.next_timestamp - 1); + } } // The sequence number needs to be recovered even though `LoadWal` didn't // load any deltas from that file. diff --git a/src/storage/v2/durability/durability.hpp b/src/storage/v2/durability/durability.hpp index 5ff0ffd08..36bb8399b 100644 --- a/src/storage/v2/durability/durability.hpp +++ b/src/storage/v2/durability/durability.hpp @@ -55,17 +55,19 @@ struct WalDurabilityInfo { explicit WalDurabilityInfo(const uint64_t seq_num, const uint64_t from_timestamp, const uint64_t to_timestamp, std::string uuid, - std::filesystem::path path) + std::string epoch_id, std::filesystem::path path) : seq_num(seq_num), from_timestamp(from_timestamp), to_timestamp(to_timestamp), uuid(std::move(uuid)), + epoch_id(std::move(epoch_id)), path(std::move(path)) {} uint64_t seq_num; uint64_t from_timestamp; uint64_t to_timestamp; std::string uuid; + std::string epoch_id; std::filesystem::path path; auto operator<=>(const WalDurabilityInfo &) const = default; @@ -100,6 +102,8 @@ void RecoverIndicesAndConstraints( std::optional RecoverData( const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, + std::deque> *epoch_history, utils::SkipList *vertices, utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, diff --git a/src/storage/v2/durability/marker.hpp b/src/storage/v2/durability/marker.hpp index ccb7ca8f1..856ee99bf 100644 --- a/src/storage/v2/durability/marker.hpp +++ b/src/storage/v2/durability/marker.hpp @@ -24,6 +24,7 @@ enum class Marker : uint8_t { SECTION_INDICES = 0x24, SECTION_CONSTRAINTS = 0x25, SECTION_DELTA = 0x26, + SECTION_EPOCH_HISTORY = 0x27, SECTION_OFFSETS = 0x42, DELTA_VERTEX_CREATE = 0x50, @@ -66,6 +67,7 @@ static const Marker kMarkersAll[] = { Marker::SECTION_INDICES, Marker::SECTION_CONSTRAINTS, Marker::SECTION_DELTA, + Marker::SECTION_EPOCH_HISTORY, Marker::SECTION_OFFSETS, Marker::DELTA_VERTEX_CREATE, Marker::DELTA_VERTEX_DELETE, diff --git a/src/storage/v2/durability/serialization.cpp b/src/storage/v2/durability/serialization.cpp index 2adf76803..5fecf1db2 100644 --- a/src/storage/v2/durability/serialization.cpp +++ b/src/storage/v2/durability/serialization.cpp @@ -317,6 +317,7 @@ std::optional Decoder::ReadPropertyValue() { case Marker::SECTION_INDICES: case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: + case Marker::SECTION_EPOCH_HISTORY: case Marker::SECTION_OFFSETS: case Marker::DELTA_VERTEX_CREATE: case Marker::DELTA_VERTEX_DELETE: @@ -412,6 +413,7 @@ bool Decoder::SkipPropertyValue() { case Marker::SECTION_INDICES: case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: + case Marker::SECTION_EPOCH_HISTORY: case Marker::SECTION_OFFSETS: case Marker::DELTA_VERTEX_CREATE: case Marker::DELTA_VERTEX_DELETE: diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp index 6b88b4b5c..97653e030 100644 --- a/src/storage/v2/durability/snapshot.cpp +++ b/src/storage/v2/durability/snapshot.cpp @@ -114,6 +114,7 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { info.offset_indices = read_offset(); info.offset_constraints = read_offset(); info.offset_mapper = read_offset(); + info.offset_epoch_history = read_offset(); info.offset_metadata = read_offset(); } @@ -130,6 +131,10 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { if (!maybe_uuid) throw RecoveryFailure("Invalid snapshot data!"); info.uuid = std::move(*maybe_uuid); + auto maybe_epoch_id = snapshot.ReadString(); + if (!maybe_epoch_id) throw RecoveryFailure("Invalid snapshot data!"); + info.epoch_id = std::move(*maybe_epoch_id); + auto maybe_timestamp = snapshot.ReadUint(); if (!maybe_timestamp) throw RecoveryFailure("Invalid snapshot data!"); info.start_timestamp = *maybe_timestamp; @@ -146,12 +151,12 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { return info; } -RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, - utils::SkipList *vertices, - utils::SkipList *edges, - NameIdMapper *name_id_mapper, - std::atomic *edge_count, - Config::Items items) { +RecoveredSnapshot LoadSnapshot( + const std::filesystem::path &path, utils::SkipList *vertices, + utils::SkipList *edges, + std::deque> *epoch_history, + NameIdMapper *name_id_mapper, std::atomic *edge_count, + Config::Items items) { RecoveryInfo ret; RecoveredIndicesAndConstraints indices_constraints; @@ -567,6 +572,34 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, } } + // Recover epoch history + { + if (!snapshot.SetPosition(info.offset_epoch_history)) + throw RecoveryFailure("Couldn't read data from snapshot!"); + + const auto marker = snapshot.ReadMarker(); + if (!marker || *marker != Marker::SECTION_EPOCH_HISTORY) + throw RecoveryFailure("Invalid snapshot data!"); + + const auto history_size = snapshot.ReadUint(); + if (!history_size) { + throw RecoveryFailure("Invalid snapshot data!"); + } + + for (int i = 0; i < *history_size; ++i) { + auto maybe_epoch_id = snapshot.ReadString(); + if (!maybe_epoch_id) { + throw RecoveryFailure("Invalid snapshot data!"); + } + const auto maybe_last_commit_timestamp = snapshot.ReadUint(); + if (!maybe_last_commit_timestamp) { + throw RecoveryFailure("Invalid snapshot data!"); + } + epoch_history->emplace_back(std::move(*maybe_epoch_id), + *maybe_last_commit_timestamp); + } + } + // Recover timestamp. ret.next_timestamp = info.start_timestamp + 1; @@ -576,15 +609,15 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, return {info, ret, std::move(indices_constraints)}; } -void CreateSnapshot(Transaction *transaction, - const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, - uint64_t snapshot_retention_count, - utils::SkipList *vertices, - utils::SkipList *edges, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, - Config::Items items, const std::string &uuid, - utils::FileRetainer *file_retainer) { +void CreateSnapshot( + Transaction *transaction, const std::filesystem::path &snapshot_directory, + const std::filesystem::path &wal_directory, + uint64_t snapshot_retention_count, utils::SkipList *vertices, + utils::SkipList *edges, NameIdMapper *name_id_mapper, + Indices *indices, Constraints *constraints, Config::Items items, + const std::string &uuid, const std::string_view epoch_id, + const std::deque> &epoch_history, + utils::FileRetainer *file_retainer) { // Ensure that the storage directory exists. utils::EnsureDirOrDie(snapshot_directory); @@ -603,6 +636,7 @@ void CreateSnapshot(Transaction *transaction, uint64_t offset_constraints = 0; uint64_t offset_mapper = 0; uint64_t offset_metadata = 0; + uint64_t offset_epoch_history = 0; { snapshot.WriteMarker(Marker::SECTION_OFFSETS); offset_offsets = snapshot.GetPosition(); @@ -611,6 +645,7 @@ void CreateSnapshot(Transaction *transaction, snapshot.WriteUint(offset_indices); snapshot.WriteUint(offset_constraints); snapshot.WriteUint(offset_mapper); + snapshot.WriteUint(offset_epoch_history); snapshot.WriteUint(offset_metadata); } @@ -814,11 +849,23 @@ void CreateSnapshot(Transaction *transaction, } } + // Write epoch history + { + offset_epoch_history = snapshot.GetPosition(); + snapshot.WriteMarker(Marker::SECTION_EPOCH_HISTORY); + snapshot.WriteUint(epoch_history.size()); + for (const auto &[epoch_id, last_commit_timestamp] : epoch_history) { + snapshot.WriteString(epoch_id); + snapshot.WriteUint(last_commit_timestamp); + } + } + // Write metadata. { offset_metadata = snapshot.GetPosition(); snapshot.WriteMarker(Marker::SECTION_METADATA); snapshot.WriteString(uuid); + snapshot.WriteString(epoch_id); snapshot.WriteUint(transaction->start_timestamp); snapshot.WriteUint(edges_count); snapshot.WriteUint(vertices_count); @@ -832,6 +879,7 @@ void CreateSnapshot(Transaction *transaction, snapshot.WriteUint(offset_indices); snapshot.WriteUint(offset_constraints); snapshot.WriteUint(offset_mapper); + snapshot.WriteUint(offset_epoch_history); snapshot.WriteUint(offset_metadata); } diff --git a/src/storage/v2/durability/snapshot.hpp b/src/storage/v2/durability/snapshot.hpp index a7d317b83..2c7792cf7 100644 --- a/src/storage/v2/durability/snapshot.hpp +++ b/src/storage/v2/durability/snapshot.hpp @@ -24,9 +24,11 @@ struct SnapshotInfo { uint64_t offset_indices; uint64_t offset_constraints; uint64_t offset_mapper; + uint64_t offset_epoch_history; uint64_t offset_metadata; std::string uuid; + std::string epoch_id; uint64_t start_timestamp; uint64_t edges_count; uint64_t vertices_count; @@ -46,22 +48,22 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path); /// Function used to load the snapshot data into the storage. /// @throw RecoveryFailure -RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, - utils::SkipList *vertices, - utils::SkipList *edges, - NameIdMapper *name_id_mapper, - std::atomic *edge_count, - Config::Items items); +RecoveredSnapshot LoadSnapshot( + const std::filesystem::path &path, utils::SkipList *vertices, + utils::SkipList *edges, + std::deque> *epoch_history, + NameIdMapper *name_id_mapper, std::atomic *edge_count, + Config::Items items); /// Function used to create a snapshot using the given transaction. -void CreateSnapshot(Transaction *transaction, - const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, - uint64_t snapshot_retention_count, - utils::SkipList *vertices, - utils::SkipList *edges, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, - Config::Items items, const std::string &uuid, - utils::FileRetainer *file_retainer); +void CreateSnapshot( + Transaction *transaction, const std::filesystem::path &snapshot_directory, + const std::filesystem::path &wal_directory, + uint64_t snapshot_retention_count, utils::SkipList *vertices, + utils::SkipList *edges, NameIdMapper *name_id_mapper, + Indices *indices, Constraints *constraints, Config::Items items, + const std::string &uuid, std::string_view epoch_id, + const std::deque> &epoch_history, + utils::FileRetainer *file_retainer); } // namespace storage::durability diff --git a/src/storage/v2/durability/version.hpp b/src/storage/v2/durability/version.hpp index 044522c22..a627017e1 100644 --- a/src/storage/v2/durability/version.hpp +++ b/src/storage/v2/durability/version.hpp @@ -9,9 +9,9 @@ namespace storage::durability { // The current version of snapshot and WAL encoding / decoding. // IMPORTANT: Please bump this version for every snapshot and/or WAL format // change!!! -const uint64_t kVersion{13}; +const uint64_t kVersion{14}; -const uint64_t kOldestSupportedVersion{12}; +const uint64_t kOldestSupportedVersion{14}; const uint64_t kUniqueConstraintVersion{13}; // Magic values written to the start of a snapshot/WAL file to identify it. diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp index 2e31fa15a..a278d99dc 100644 --- a/src/storage/v2/durability/wal.cpp +++ b/src/storage/v2/durability/wal.cpp @@ -165,6 +165,7 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { case Marker::SECTION_INDICES: case Marker::SECTION_CONSTRAINTS: case Marker::SECTION_DELTA: + case Marker::SECTION_EPOCH_HISTORY: case Marker::SECTION_OFFSETS: case Marker::VALUE_FALSE: case Marker::VALUE_TRUE: @@ -172,39 +173,6 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) { } } -bool IsWalDeltaDataTypeTransactionEnd(WalDeltaData::Type type) { - switch (type) { - // These delta actions are all found inside transactions so they don't - // indicate a transaction end. - case WalDeltaData::Type::VERTEX_CREATE: - case WalDeltaData::Type::VERTEX_DELETE: - case WalDeltaData::Type::VERTEX_ADD_LABEL: - case WalDeltaData::Type::VERTEX_REMOVE_LABEL: - case WalDeltaData::Type::EDGE_CREATE: - case WalDeltaData::Type::EDGE_DELETE: - case WalDeltaData::Type::VERTEX_SET_PROPERTY: - case WalDeltaData::Type::EDGE_SET_PROPERTY: - return false; - - // This delta explicitly indicates that a transaction is done. - case WalDeltaData::Type::TRANSACTION_END: - return true; - - // These operations aren't transactional and they are encoded only using - // a single delta, so they each individually mark the end of their - // 'transaction'. - case WalDeltaData::Type::LABEL_INDEX_CREATE: - case WalDeltaData::Type::LABEL_INDEX_DROP: - case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: - case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: - case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: - case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: - case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: - case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: - return true; - } -} - // Function used to either read or skip the current WAL delta data. The WAL // delta header must be read before calling this function. If the delta data is // read then the data returned is valid, if the delta data is skipped then the @@ -386,6 +354,10 @@ WalInfo ReadWalInfo(const std::filesystem::path &path) { if (!maybe_uuid) throw RecoveryFailure("Invalid WAL data!"); info.uuid = std::move(*maybe_uuid); + auto maybe_epoch_id = wal.ReadString(); + if (!maybe_epoch_id) throw RecoveryFailure("Invalid WAL data!"); + info.epoch_id = std::move(*maybe_epoch_id); + auto maybe_seq_num = wal.ReadUint(); if (!maybe_seq_num) throw RecoveryFailure("Invalid WAL data!"); info.seq_num = *maybe_seq_num; @@ -970,9 +942,9 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, } WalFile::WalFile(const std::filesystem::path &wal_directory, - const std::string &uuid, Config::Items items, - NameIdMapper *name_id_mapper, uint64_t seq_num, - utils::FileRetainer *file_retainer) + const std::string_view uuid, const std::string_view epoch_id, + Config::Items items, NameIdMapper *name_id_mapper, + uint64_t seq_num, utils::FileRetainer *file_retainer) : items_(items), name_id_mapper_(name_id_mapper), path_(wal_directory / MakeWalName()), @@ -1000,6 +972,7 @@ WalFile::WalFile(const std::filesystem::path &wal_directory, offset_metadata = wal_.GetPosition(); wal_.WriteMarker(Marker::SECTION_METADATA); wal_.WriteString(uuid); + wal_.WriteString(epoch_id); wal_.WriteUint(seq_num); // Write final offsets. diff --git a/src/storage/v2/durability/wal.hpp b/src/storage/v2/durability/wal.hpp index 1c7cf0174..93ebe02c7 100644 --- a/src/storage/v2/durability/wal.hpp +++ b/src/storage/v2/durability/wal.hpp @@ -25,6 +25,7 @@ struct WalInfo { uint64_t offset_deltas; std::string uuid; + std::string epoch_id; uint64_t seq_num; uint64_t from_timestamp; uint64_t to_timestamp; @@ -107,6 +108,39 @@ enum class StorageGlobalOperation { UNIQUE_CONSTRAINT_DROP, }; +constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) { + switch (type) { + // These delta actions are all found inside transactions so they don't + // indicate a transaction end. + case WalDeltaData::Type::VERTEX_CREATE: + case WalDeltaData::Type::VERTEX_DELETE: + case WalDeltaData::Type::VERTEX_ADD_LABEL: + case WalDeltaData::Type::VERTEX_REMOVE_LABEL: + case WalDeltaData::Type::EDGE_CREATE: + case WalDeltaData::Type::EDGE_DELETE: + case WalDeltaData::Type::VERTEX_SET_PROPERTY: + case WalDeltaData::Type::EDGE_SET_PROPERTY: + return false; + + // This delta explicitly indicates that a transaction is done. + case WalDeltaData::Type::TRANSACTION_END: + return true; + + // These operations aren't transactional and they are encoded only using + // a single delta, so they each individually mark the end of their + // 'transaction'. + case WalDeltaData::Type::LABEL_INDEX_CREATE: + case WalDeltaData::Type::LABEL_INDEX_DROP: + case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: + case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: + case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: + case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: + case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: + case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: + return true; + } +} + /// Function used to read information about the WAL file. /// @throw RecoveryFailure WalInfo ReadWalInfo(const std::filesystem::path &path); @@ -158,8 +192,9 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, /// WalFile class used to append deltas and operations to the WAL file. class WalFile { public: - WalFile(const std::filesystem::path &wal_directory, const std::string &uuid, - Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, + WalFile(const std::filesystem::path &wal_directory, std::string_view uuid, + std::string_view epoch_id, Config::Items items, + NameIdMapper *name_id_mapper, uint64_t seq_num, utils::FileRetainer *file_retainer); WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, diff --git a/src/storage/v2/replication/enums.hpp b/src/storage/v2/replication/enums.hpp new file mode 100644 index 000000000..1407c3ee2 --- /dev/null +++ b/src/storage/v2/replication/enums.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace storage::replication { +enum class ReplicationMode : std::uint8_t { SYNC, ASYNC }; + +enum class ReplicaState : std::uint8_t { + READY, + REPLICATING, + RECOVERY, + INVALID +}; +} // namespace storage::replication diff --git a/src/storage/v2/replication/replication.cpp b/src/storage/v2/replication/replication_client.cpp similarity index 67% rename from src/storage/v2/replication/replication.cpp rename to src/storage/v2/replication/replication_client.cpp index 0eb1050ec..cedcb6f02 100644 --- a/src/storage/v2/replication/replication.cpp +++ b/src/storage/v2/replication/replication_client.cpp @@ -1,12 +1,13 @@ -#include "storage/v2/replication/replication.hpp" +#include "storage/v2/replication/replication_client.hpp" #include #include #include "storage/v2/durability/durability.hpp" +#include "storage/v2/replication/enums.hpp" #include "utils/file_locker.hpp" -namespace storage::replication { +namespace storage { namespace { template @@ -14,63 +15,68 @@ template } // namespace ////// ReplicationClient ////// -ReplicationClient::ReplicationClient( - std::string name, const std::atomic &last_commit_timestamp, - NameIdMapper *name_id_mapper, Config::Items items, - utils::FileRetainer *file_retainer, - const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, const std::string_view uuid, - std::optional *wal_file_ptr, - utils::SpinLock *transaction_engine_lock, - const io::network::Endpoint &endpoint, bool use_ssl, - const ReplicationMode mode) +Storage::ReplicationClient::ReplicationClient( + std::string name, Storage *storage, const io::network::Endpoint &endpoint, + bool use_ssl, const replication::ReplicationMode mode) : name_(std::move(name)), - last_commit_timestamp_{last_commit_timestamp}, - name_id_mapper_(name_id_mapper), - items_(items), - file_retainer_(file_retainer), - snapshot_directory_(snapshot_directory), - wal_directory_(wal_directory), - uuid_(uuid), - wal_file_ptr_(wal_file_ptr), - transaction_engine_lock_(transaction_engine_lock), + storage_(storage), rpc_context_(use_ssl), rpc_client_(endpoint, &rpc_context_), mode_(mode) { InitializeClient(); } -void ReplicationClient::InitializeClient() { +void Storage::ReplicationClient::InitializeClient() { uint64_t current_commit_timestamp{kTimestampInitialId}; - auto stream{rpc_client_.Stream()}; + auto stream{ + rpc_client_.Stream(storage_->last_commit_timestamp_)}; + replication::Encoder encoder{stream.GetBuilder()}; + // Write epoch id + { + // We need to lock so the epoch id isn't overwritten + std::unique_lock engine_guard{storage_->engine_lock_}; + encoder.WriteString(storage_->epoch_id_); + } const auto response = stream.AwaitResponse(); + if (!response.success) { + LOG(ERROR) + << "Replica " << name_ + << " is ahead of this instance. The branching point is on commit " + << response.current_commit_timestamp; + return; + } current_commit_timestamp = response.current_commit_timestamp; DLOG(INFO) << "CURRENT TIMESTAMP: " << current_commit_timestamp; - DLOG(INFO) << "CURRENT MAIN TIMESTAMP: " << last_commit_timestamp_.load(); - if (current_commit_timestamp == last_commit_timestamp_.load()) { + DLOG(INFO) << "CURRENT MAIN TIMESTAMP: " + << storage_->last_commit_timestamp_.load(); + if (current_commit_timestamp == storage_->last_commit_timestamp_.load()) { DLOG(INFO) << "REPLICA UP TO DATE"; - replica_state_.store(ReplicaState::READY); + std::unique_lock client_guard{client_lock_}; + replica_state_.store(replication::ReplicaState::READY); } else { DLOG(INFO) << "REPLICA IS BEHIND"; - replica_state_.store(ReplicaState::RECOVERY); + { + std::unique_lock client_guard{client_lock_}; + replica_state_.store(replication::ReplicaState::RECOVERY); + } thread_pool_.AddTask( [=, this] { this->RecoverReplica(current_commit_timestamp); }); } } -SnapshotRes ReplicationClient::TransferSnapshot( +SnapshotRes Storage::ReplicationClient::TransferSnapshot( const std::filesystem::path &path) { auto stream{rpc_client_.Stream()}; - Encoder encoder(stream.GetBuilder()); + replication::Encoder encoder(stream.GetBuilder()); encoder.WriteFile(path); return stream.AwaitResponse(); } -WalFilesRes ReplicationClient::TransferWalFiles( +WalFilesRes Storage::ReplicationClient::TransferWalFiles( const std::vector &wal_files) { CHECK(!wal_files.empty()) << "Wal files list is empty!"; auto stream{rpc_client_.Stream(wal_files.size())}; - Encoder encoder(stream.GetBuilder()); + replication::Encoder encoder(stream.GetBuilder()); for (const auto &wal : wal_files) { DLOG(INFO) << "Sending wal file: " << wal; encoder.WriteFile(wal); @@ -79,47 +85,56 @@ WalFilesRes ReplicationClient::TransferWalFiles( return stream.AwaitResponse(); } -OnlySnapshotRes ReplicationClient::TransferOnlySnapshot( +OnlySnapshotRes Storage::ReplicationClient::TransferOnlySnapshot( const uint64_t snapshot_timestamp) { auto stream{rpc_client_.Stream(snapshot_timestamp)}; + replication::Encoder encoder{stream.GetBuilder()}; + encoder.WriteString(storage_->epoch_id_); return stream.AwaitResponse(); } -bool ReplicationClient::StartTransactionReplication( +bool Storage::ReplicationClient::StartTransactionReplication( const uint64_t current_wal_seq_num) { std::unique_lock guard(client_lock_); const auto status = replica_state_.load(); switch (status) { - case ReplicaState::RECOVERY: + case replication::ReplicaState::RECOVERY: DLOG(INFO) << "Replica " << name_ << " is behind MAIN instance"; return false; - case ReplicaState::REPLICATING: + case replication::ReplicaState::REPLICATING: DLOG(INFO) << "Replica missed a transaction, going to recovery"; - replica_state_.store(ReplicaState::RECOVERY); + replica_state_.store(replication::ReplicaState::RECOVERY); // If it's in replicating state, it should have been up to date with all // the commits until now so the replica should contain the // last_commit_timestamp - thread_pool_.AddTask( - [=, this] { this->RecoverReplica(last_commit_timestamp_.load()); }); + thread_pool_.AddTask([=, this] { + this->RecoverReplica(storage_->last_commit_timestamp_.load()); + }); return false; - case ReplicaState::READY: + case replication::ReplicaState::INVALID: + LOG(ERROR) << "Couldn't replicate data to " << name_; + return false; + case replication::ReplicaState::READY: CHECK(!replica_stream_); try { - replica_stream_.emplace(ReplicaStream{ - this, last_commit_timestamp_.load(), current_wal_seq_num}); + replica_stream_.emplace( + ReplicaStream{this, storage_->last_commit_timestamp_.load(), + current_wal_seq_num}); } catch (const rpc::RpcFailedException &) { + replica_state_.store(replication::ReplicaState::INVALID); LOG(ERROR) << "Couldn't replicate data to " << name_; thread_pool_.AddTask([this] { rpc_client_.Abort(); InitializeClient(); }); + return false; } - replica_state_.store(ReplicaState::REPLICATING); + replica_state_.store(replication::ReplicaState::REPLICATING); return true; } } -void ReplicationClient::IfStreamingTransaction( +void Storage::ReplicationClient::IfStreamingTransaction( const std::function &callback) { if (replica_stream_) { try { @@ -134,8 +149,8 @@ void ReplicationClient::IfStreamingTransaction( } } -void ReplicationClient::FinalizeTransactionReplication() { - if (mode_ == ReplicationMode::ASYNC) { +void Storage::ReplicationClient::FinalizeTransactionReplication() { + if (mode_ == replication::ReplicationMode::ASYNC) { thread_pool_.AddTask( [this] { this->FinalizeTransactionReplicationInternal(); }); } else { @@ -143,18 +158,25 @@ void ReplicationClient::FinalizeTransactionReplication() { } } -void ReplicationClient::FinalizeTransactionReplicationInternal() { +void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() { if (replica_stream_) { try { auto response = replica_stream_->Finalize(); if (!response.success) { - replica_state_.store(ReplicaState::RECOVERY); + { + std::unique_lock client_guard{client_lock_}; + replica_state_.store(replication::ReplicaState::RECOVERY); + } thread_pool_.AddTask([&, this] { this->RecoverReplica(response.current_commit_timestamp); }); } } catch (const rpc::RpcFailedException &) { LOG(ERROR) << "Couldn't replicate data to " << name_; + { + std::unique_lock client_guard{client_lock_}; + replica_state_.store(replication::ReplicaState::INVALID); + } thread_pool_.AddTask([this] { rpc_client_.Abort(); InitializeClient(); @@ -164,14 +186,14 @@ void ReplicationClient::FinalizeTransactionReplicationInternal() { } std::unique_lock guard(client_lock_); - if (replica_state_.load() == ReplicaState::REPLICATING) { - replica_state_.store(ReplicaState::READY); + if (replica_state_.load() == replication::ReplicaState::REPLICATING) { + replica_state_.store(replication::ReplicaState::READY); } } -void ReplicationClient::RecoverReplica(uint64_t replica_commit) { +void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { while (true) { - auto file_locker = file_retainer_->AddLocker(); + auto file_locker = storage_->file_retainer_.AddLocker(); const auto steps = GetRecoverySteps(replica_commit, &file_locker); for (const auto &recovery_step : steps) { @@ -189,17 +211,17 @@ void ReplicationClient::RecoverReplica(uint64_t replica_commit) { replica_commit = response.current_commit_timestamp; DLOG(INFO) << "CURRENT TIMESTAMP ON REPLICA: " << replica_commit; } else if constexpr (std::is_same_v) { - auto &wal_file = *wal_file_ptr_; - std::unique_lock transaction_guard(*transaction_engine_lock_); - if (wal_file && - wal_file->SequenceNumber() == arg.current_wal_seq_num) { - wal_file->DisableFlushing(); + std::unique_lock transaction_guard(storage_->engine_lock_); + if (storage_->wal_file_ && + storage_->wal_file_->SequenceNumber() == + arg.current_wal_seq_num) { + storage_->wal_file_->DisableFlushing(); transaction_guard.unlock(); DLOG(INFO) << "Sending current wal file"; replica_commit = ReplicateCurrentWal(); DLOG(INFO) << "CURRENT TIMESTAMP ON REPLICA: " << replica_commit; - wal_file->EnableFlushing(); + storage_->wal_file_->EnableFlushing(); } } else if constexpr (std::is_same_v) { @@ -216,20 +238,21 @@ void ReplicationClient::RecoverReplica(uint64_t replica_commit) { recovery_step); } - if (last_commit_timestamp_.load() == replica_commit) { - replica_state_.store(ReplicaState::READY); + if (storage_->last_commit_timestamp_.load() == replica_commit) { + std::unique_lock client_guard{client_lock_}; + replica_state_.store(replication::ReplicaState::READY); return; } } } -uint64_t ReplicationClient::ReplicateCurrentWal() { - auto &wal_file = *wal_file_ptr_; +uint64_t Storage::ReplicationClient::ReplicateCurrentWal() { auto stream = TransferCurrentWalFile(); - stream.AppendFilename(wal_file->Path().filename()); + stream.AppendFilename(storage_->wal_file_->Path().filename()); utils::InputFile file; - CHECK(file.Open(wal_file->Path())) << "Failed to open current WAL file!"; - const auto [buffer, buffer_size] = wal_file->CurrentFileBuffer(); + CHECK(file.Open(storage_->wal_file_->Path())) + << "Failed to open current WAL file!"; + const auto [buffer, buffer_size] = storage_->wal_file_->CurrentFileBuffer(); stream.AppendSize(file.GetSize() + buffer_size); stream.AppendFileData(&file); stream.AppendBufferData(buffer, buffer_size); @@ -259,26 +282,26 @@ uint64_t ReplicationClient::ReplicateCurrentWal() { /// change (creation of that snapshot) the latest timestamp is contained in it. /// As no changes were made to the data, we only need to send the timestamp of /// the snapshot so replica can set its last timestamp to that value. -std::vector -ReplicationClient::GetRecoverySteps( +std::vector +Storage::ReplicationClient::GetRecoverySteps( const uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker) { - auto &wal_file = *wal_file_ptr_; // First check if we can recover using the current wal file only // otherwise save the seq_num of the current wal file // This lock is also necessary to force the missed transaction to finish. std::optional current_wal_seq_num; - if (std::unique_lock transtacion_guard(*transaction_engine_lock_); wal_file) { - current_wal_seq_num.emplace(wal_file->SequenceNumber()); + if (std::unique_lock transtacion_guard(storage_->engine_lock_); + storage_->wal_file_) { + current_wal_seq_num.emplace(storage_->wal_file_->SequenceNumber()); } auto locker_acc = file_locker->Access(); - auto wal_files = - durability::GetWalFiles(wal_directory_, uuid_, current_wal_seq_num); + auto wal_files = durability::GetWalFiles( + storage_->wal_directory_, storage_->uuid_, current_wal_seq_num); CHECK(wal_files) << "Wal files could not be loaded"; - auto snapshot_files = - durability::GetSnapshotFiles(snapshot_directory_, uuid_); + auto snapshot_files = durability::GetSnapshotFiles( + storage_->snapshot_directory_, storage_->uuid_); std::optional latest_snapshot; if (!snapshot_files.empty()) { std::sort(snapshot_files.begin(), snapshot_files.end()); @@ -404,73 +427,79 @@ ReplicationClient::GetRecoverySteps( } ////// ReplicaStream ////// -ReplicationClient::ReplicaStream::ReplicaStream( +Storage::ReplicationClient::ReplicaStream::ReplicaStream( ReplicationClient *self, const uint64_t previous_commit_timestamp, const uint64_t current_seq_num) : self_(self), stream_(self_->rpc_client_.Stream( - previous_commit_timestamp, current_seq_num)) {} + previous_commit_timestamp, current_seq_num)) { + replication::Encoder encoder{stream_.GetBuilder()}; + encoder.WriteString(self_->storage_->epoch_id_); +} -void ReplicationClient::ReplicaStream::AppendDelta( +void Storage::ReplicationClient::ReplicaStream::AppendDelta( const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp) { - Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, self_->name_id_mapper_, self_->items_, delta, vertex, + replication::Encoder encoder(stream_.GetBuilder()); + EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, + self_->storage_->config_.items, delta, vertex, final_commit_timestamp); } -void ReplicationClient::ReplicaStream::AppendDelta( +void Storage::ReplicationClient::ReplicaStream::AppendDelta( const Delta &delta, const Edge &edge, uint64_t final_commit_timestamp) { - Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, self_->name_id_mapper_, delta, edge, + replication::Encoder encoder(stream_.GetBuilder()); + EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, delta, edge, final_commit_timestamp); } -void ReplicationClient::ReplicaStream::AppendTransactionEnd( +void Storage::ReplicationClient::ReplicaStream::AppendTransactionEnd( uint64_t final_commit_timestamp) { - Encoder encoder(stream_.GetBuilder()); + replication::Encoder encoder(stream_.GetBuilder()); EncodeTransactionEnd(&encoder, final_commit_timestamp); } -void ReplicationClient::ReplicaStream::AppendOperation( +void Storage::ReplicationClient::ReplicaStream::AppendOperation( durability::StorageGlobalOperation operation, LabelId label, const std::set &properties, uint64_t timestamp) { - Encoder encoder(stream_.GetBuilder()); - EncodeOperation(&encoder, self_->name_id_mapper_, operation, label, + replication::Encoder encoder(stream_.GetBuilder()); + EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, label, properties, timestamp); } -AppendDeltasRes ReplicationClient::ReplicaStream::Finalize() { +AppendDeltasRes Storage::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); } ////// CurrentWalHandler ////// -ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self) +Storage::ReplicationClient::CurrentWalHandler::CurrentWalHandler( + ReplicationClient *self) : self_(self), stream_(self_->rpc_client_.Stream()) {} -void ReplicationClient::CurrentWalHandler::AppendFilename( +void Storage::ReplicationClient::CurrentWalHandler::AppendFilename( const std::string &filename) { - Encoder encoder(stream_.GetBuilder()); + replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteString(filename); } -void ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) { - Encoder encoder(stream_.GetBuilder()); +void Storage::ReplicationClient::CurrentWalHandler::AppendSize( + const size_t size) { + replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteUint(size); } -void ReplicationClient::CurrentWalHandler::AppendFileData( +void Storage::ReplicationClient::CurrentWalHandler::AppendFileData( utils::InputFile *file) { - Encoder encoder(stream_.GetBuilder()); + replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteFileData(file); } -void ReplicationClient::CurrentWalHandler::AppendBufferData( +void Storage::ReplicationClient::CurrentWalHandler::AppendBufferData( const uint8_t *buffer, const size_t buffer_size) { - Encoder encoder(stream_.GetBuilder()); + replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteBuffer(buffer, buffer_size); } -CurrentWalRes ReplicationClient::CurrentWalHandler::Finalize() { +CurrentWalRes Storage::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); } -} // namespace storage::replication +} // namespace storage diff --git a/src/storage/v2/replication/replication.hpp b/src/storage/v2/replication/replication_client.hpp similarity index 78% rename from src/storage/v2/replication/replication.hpp rename to src/storage/v2/replication/replication_client.hpp index d41e2fc77..d4a37c698 100644 --- a/src/storage/v2/replication/replication.hpp +++ b/src/storage/v2/replication/replication_client.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -13,33 +12,23 @@ #include "storage/v2/mvcc.hpp" #include "storage/v2/name_id_mapper.hpp" #include "storage/v2/property_value.hpp" +#include "storage/v2/replication/enums.hpp" #include "storage/v2/replication/rpc.hpp" #include "storage/v2/replication/serialization.hpp" +#include "storage/v2/storage.hpp" #include "utils/file.hpp" #include "utils/file_locker.hpp" #include "utils/spin_lock.hpp" #include "utils/synchronized.hpp" #include "utils/thread_pool.hpp" -namespace storage::replication { +namespace storage { -enum class ReplicationMode : std::uint8_t { SYNC, ASYNC }; - -enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY }; - -class ReplicationClient { +class Storage::ReplicationClient { public: - ReplicationClient(std::string name, - const std::atomic &last_commit_timestamp, - NameIdMapper *name_id_mapper, Config::Items items, - utils::FileRetainer *file_retainer, - const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, - std::string_view uuid, - std::optional *wal_file_ptr, - utils::SpinLock *transaction_engine_lock, + ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint, bool use_ssl, - ReplicationMode mode); + replication::ReplicationMode mode); // Handler used for transfering the current transaction. class ReplicaStream { @@ -158,26 +147,19 @@ class ReplicationClient { void InitializeClient(); std::string name_; - // storage info - const std::atomic &last_commit_timestamp_; - NameIdMapper *name_id_mapper_; - Config::Items items_; - utils::FileRetainer *file_retainer_; - const std::filesystem::path &snapshot_directory_; - const std::filesystem::path &wal_directory_; - std::string_view uuid_; - std::optional *wal_file_ptr_; - utils::SpinLock *transaction_engine_lock_; + + Storage *storage_; communication::ClientContext rpc_context_; rpc::Client rpc_client_; std::optional replica_stream_; - ReplicationMode mode_{ReplicationMode::SYNC}; + replication::ReplicationMode mode_{replication::ReplicationMode::SYNC}; utils::SpinLock client_lock_; utils::ThreadPool thread_pool_{1}; - std::atomic replica_state_; + std::atomic replica_state_{ + replication::ReplicaState::INVALID}; }; -} // namespace storage::replication +} // namespace storage diff --git a/src/storage/v2/replication/replication_server.cpp b/src/storage/v2/replication/replication_server.cpp new file mode 100644 index 000000000..19516ecf2 --- /dev/null +++ b/src/storage/v2/replication/replication_server.cpp @@ -0,0 +1,727 @@ +#include "storage/v2/replication/replication_server.hpp" + +#include "storage/v2/durability/durability.hpp" +#include "storage/v2/durability/snapshot.hpp" +#include "storage/v2/transaction.hpp" +#include "utils/exceptions.hpp" + +namespace storage { +Storage::ReplicationServer::ReplicationServer(Storage *storage, + io::network::Endpoint endpoint) + : storage_(storage) { + // Create RPC server. + // TODO (antonio2368): Add support for SSL. + rpc_server_context_.emplace(); + // NOTE: The replication server must have a single thread for processing + // because there is no need for more processing threads - each replica can + // have only a single main server. Also, the single-threaded guarantee + // simplifies the rest of the implementation. + rpc_server_.emplace(std::move(endpoint), &*rpc_server_context_, + /* workers_count = */ 1); + + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received HeartbeatRpc"; + this->HeartbeatHandler(req_reader, res_builder); + }); + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received AppendDeltasRpc:"; + this->AppendDeltasHandler(req_reader, res_builder); + }); + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received SnapshotRpc"; + this->SnapshotHandler(req_reader, res_builder); + }); + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received OnlySnapshotRpc"; + this->OnlySnapshotHandler(req_reader, res_builder); + }); + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received WalFilesRpc"; + this->WalFilesHandler(req_reader, res_builder); + }); + rpc_server_->Register( + [this](auto *req_reader, auto *res_builder) { + DLOG(INFO) << "Received CurrentWalRpc"; + this->CurrentWalHandler(req_reader, res_builder); + }); + rpc_server_->Start(); +} + +void Storage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, + slk::Builder *res_builder) { + HeartbeatReq req; + slk::Load(&req, req_reader); + replication::Decoder decoder{req_reader}; + auto maybe_epoch_id = decoder.ReadString(); + CHECK(maybe_epoch_id) << "Invalid value read form HeartbeatRpc!"; + if (storage_->last_commit_timestamp_ == kTimestampInitialId) { + // The replica has no commits + // use the main's epoch id + storage_->epoch_id_ = std::move(*maybe_epoch_id); + } else if (*maybe_epoch_id != storage_->epoch_id_) { + auto &epoch_history = storage_->epoch_history_; + const auto result = + std::find_if(epoch_history.rbegin(), epoch_history.rend(), + [&](const auto &epoch_info) { + return epoch_info.first == *maybe_epoch_id; + }); + auto branching_point = kTimestampInitialId; + if (result == epoch_history.rend()) { + // we couldn't find the epoch_id inside the history so if it has + // the same or larger commit timestamp, some old replica became a main + // This isn't always the case, there is one case where an old main + // becomes a replica then main again and it should have a commit timestamp + // larger than the one on replica. + if (req.main_commit_timestamp >= storage_->last_commit_timestamp_) { + epoch_history.emplace_back(std::move(storage_->epoch_id_), + storage_->last_commit_timestamp_); + storage_->epoch_id_ = std::move(*maybe_epoch_id); + HeartbeatRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); + return; + } + } else { + branching_point = result->second; + } + HeartbeatRes res{false, branching_point}; + slk::Save(res, res_builder); + return; + } + HeartbeatRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); +} + +void Storage::ReplicationServer::AppendDeltasHandler( + slk::Reader *req_reader, slk::Builder *res_builder) { + AppendDeltasReq req; + slk::Load(&req, req_reader); + + replication::Decoder decoder(req_reader); + + auto maybe_epoch_id = decoder.ReadString(); + CHECK(maybe_epoch_id) << "Invalid replication message"; + + const auto read_delta = + [&]() -> std::pair { + try { + auto timestamp = ReadWalDeltaHeader(&decoder); + DLOG(INFO) << " Timestamp " << timestamp; + auto delta = ReadWalDeltaData(&decoder); + return {timestamp, delta}; + } catch (const slk::SlkReaderException &) { + throw utils::BasicException("Missing data!"); + } catch (const durability::RecoveryFailure &) { + throw utils::BasicException("Invalid data!"); + } + }; + + // TODO (antonio2368): Add error handling for different epoch id + if (*maybe_epoch_id != storage_->epoch_id_) { + throw utils::BasicException("Invalid epoch id"); + } + + if (req.previous_commit_timestamp != + storage_->last_commit_timestamp_.load()) { + // Empty the stream + bool transaction_complete = false; + while (!transaction_complete) { + DLOG(INFO) << "Skipping delta"; + const auto [timestamp, delta] = read_delta(); + transaction_complete = + durability::IsWalDeltaDataTypeTransactionEnd(delta.type); + } + + AppendDeltasRes res{false, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); + return; + } + + if (storage_->wal_file_) { + if (req.seq_num > storage_->wal_file_->SequenceNumber() || + *maybe_epoch_id != storage_->epoch_id_) { + storage_->wal_file_->FinalizeWal(); + storage_->wal_file_.reset(); + storage_->wal_seq_num_ = req.seq_num; + } else { + CHECK(storage_->wal_file_->SequenceNumber() == req.seq_num) + << "Invalid sequence number of current wal file"; + storage_->wal_seq_num_ = req.seq_num + 1; + } + } else { + storage_->wal_seq_num_ = req.seq_num; + } + + auto edge_acc = storage_->edges_.access(); + auto vertex_acc = storage_->vertices_.access(); + + std::optional> + commit_timestamp_and_accessor; + auto get_transaction = + [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { + if (!commit_timestamp_and_accessor) { + commit_timestamp_and_accessor.emplace(commit_timestamp, + storage_->Access()); + } else if (commit_timestamp_and_accessor->first != commit_timestamp) { + throw utils::BasicException("Received more than one transaction!"); + } + return &commit_timestamp_and_accessor->second; + }; + + bool transaction_complete = false; + for (uint64_t i = 0; !transaction_complete; ++i) { + DLOG(INFO) << " Delta " << i; + const auto [timestamp, delta] = read_delta(); + + switch (delta.type) { + case durability::WalDeltaData::Type::VERTEX_CREATE: { + DLOG(INFO) << " Create vertex " + << delta.vertex_create_delete.gid.AsUint(); + auto transaction = get_transaction(timestamp); + transaction->CreateVertex(delta.vertex_create_delete.gid); + break; + } + case durability::WalDeltaData::Type::VERTEX_DELETE: { + DLOG(INFO) << " Delete vertex " + << delta.vertex_create_delete.gid.AsUint(); + auto transaction = get_transaction(timestamp); + auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, + storage::View::NEW); + if (!vertex) throw utils::BasicException("Invalid transaction!"); + auto ret = transaction->DeleteVertex(&*vertex); + if (ret.HasError() || !ret.GetValue()) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: { + DLOG(INFO) << " Vertex " + << delta.vertex_add_remove_label.gid.AsUint() + << " add label " << delta.vertex_add_remove_label.label; + auto transaction = get_transaction(timestamp); + auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, + storage::View::NEW); + if (!vertex) throw utils::BasicException("Invalid transaction!"); + auto ret = vertex->AddLabel( + transaction->NameToLabel(delta.vertex_add_remove_label.label)); + if (ret.HasError() || !ret.GetValue()) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: { + DLOG(INFO) << " Vertex " + << delta.vertex_add_remove_label.gid.AsUint() + << " remove label " << delta.vertex_add_remove_label.label; + auto transaction = get_transaction(timestamp); + auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, + storage::View::NEW); + if (!vertex) throw utils::BasicException("Invalid transaction!"); + auto ret = vertex->RemoveLabel( + transaction->NameToLabel(delta.vertex_add_remove_label.label)); + if (ret.HasError() || !ret.GetValue()) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: { + DLOG(INFO) << " Vertex " + << delta.vertex_edge_set_property.gid.AsUint() + << " set property " + << delta.vertex_edge_set_property.property << " to " + << delta.vertex_edge_set_property.value; + auto transaction = get_transaction(timestamp); + auto vertex = transaction->FindVertex( + delta.vertex_edge_set_property.gid, storage::View::NEW); + if (!vertex) throw utils::BasicException("Invalid transaction!"); + auto ret = + vertex->SetProperty(transaction->NameToProperty( + delta.vertex_edge_set_property.property), + delta.vertex_edge_set_property.value); + if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::EDGE_CREATE: { + DLOG(INFO) << " Create edge " + << delta.edge_create_delete.gid.AsUint() << " of type " + << delta.edge_create_delete.edge_type << " from vertex " + << delta.edge_create_delete.from_vertex.AsUint() + << " to vertex " + << delta.edge_create_delete.to_vertex.AsUint(); + auto transaction = get_transaction(timestamp); + auto from_vertex = transaction->FindVertex( + delta.edge_create_delete.from_vertex, storage::View::NEW); + if (!from_vertex) throw utils::BasicException("Invalid transaction!"); + auto to_vertex = transaction->FindVertex( + delta.edge_create_delete.to_vertex, storage::View::NEW); + if (!to_vertex) throw utils::BasicException("Invalid transaction!"); + auto edge = transaction->CreateEdge( + &*from_vertex, &*to_vertex, + transaction->NameToEdgeType(delta.edge_create_delete.edge_type), + delta.edge_create_delete.gid); + if (edge.HasError()) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::EDGE_DELETE: { + DLOG(INFO) << " Delete edge " + << delta.edge_create_delete.gid.AsUint() << " of type " + << delta.edge_create_delete.edge_type << " from vertex " + << delta.edge_create_delete.from_vertex.AsUint() + << " to vertex " + << delta.edge_create_delete.to_vertex.AsUint(); + auto transaction = get_transaction(timestamp); + auto from_vertex = transaction->FindVertex( + delta.edge_create_delete.from_vertex, storage::View::NEW); + if (!from_vertex) throw utils::BasicException("Invalid transaction!"); + auto to_vertex = transaction->FindVertex( + delta.edge_create_delete.to_vertex, storage::View::NEW); + if (!to_vertex) throw utils::BasicException("Invalid transaction!"); + auto edges = from_vertex->OutEdges( + storage::View::NEW, + {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)}, + &*to_vertex); + if (edges.HasError()) + throw utils::BasicException("Invalid transaction!"); + if (edges->size() != 1) + throw utils::BasicException("Invalid transaction!"); + auto &edge = (*edges)[0]; + auto ret = transaction->DeleteEdge(&edge); + if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: { + DLOG(INFO) << " Edge " + << delta.vertex_edge_set_property.gid.AsUint() + << " set property " + << delta.vertex_edge_set_property.property << " to " + << delta.vertex_edge_set_property.value; + + if (!storage_->config_.items.properties_on_edges) + throw utils::BasicException( + "Can't set properties on edges because properties on edges " + "are disabled!"); + + auto transaction = get_transaction(timestamp); + + // The following block of code effectively implements `FindEdge` and + // yields an accessor that is only valid for managing the edge's + // properties. + auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); + if (edge == edge_acc.end()) + throw utils::BasicException("Invalid transaction!"); + // The edge visibility check must be done here manually because we + // don't allow direct access to the edges through the public API. + { + bool is_visible = true; + Delta *delta = nullptr; + { + std::lock_guard guard(edge->lock); + is_visible = !edge->deleted; + delta = edge->delta; + } + ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, + [&is_visible](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: { + is_visible = true; + break; + } + case Delta::Action::DELETE_OBJECT: { + is_visible = false; + break; + } + } + }); + if (!is_visible) throw utils::BasicException("Invalid transaction!"); + } + EdgeRef edge_ref(&*edge); + // Here we create an edge accessor that we will use to get the + // properties of the edge. The accessor is created with an invalid + // type and invalid from/to pointers because we don't know them + // here, but that isn't an issue because we won't use that part of + // the API here. + auto ea = EdgeAccessor{edge_ref, + EdgeTypeId::FromUint(0UL), + nullptr, + nullptr, + &transaction->transaction_, + &storage_->indices_, + &storage_->constraints_, + storage_->config_.items}; + + auto ret = ea.SetProperty(transaction->NameToProperty( + delta.vertex_edge_set_property.property), + delta.vertex_edge_set_property.value); + if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + break; + } + + case durability::WalDeltaData::Type::TRANSACTION_END: { + DLOG(INFO) << " Transaction end"; + if (!commit_timestamp_and_accessor || + commit_timestamp_and_accessor->first != timestamp) + throw utils::BasicException("Invalid data!"); + auto ret = commit_timestamp_and_accessor->second.Commit( + commit_timestamp_and_accessor->first); + if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + commit_timestamp_and_accessor = std::nullopt; + break; + } + + case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: { + DLOG(INFO) << " Create label index on :" + << delta.operation_label.label; + // Need to send the timestamp + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + if (!storage_->CreateIndex( + storage_->NameToLabel(delta.operation_label.label), timestamp)) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::LABEL_INDEX_DROP: { + DLOG(INFO) << " Drop label index on :" + << delta.operation_label.label; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + if (!storage_->DropIndex( + storage_->NameToLabel(delta.operation_label.label), timestamp)) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { + DLOG(INFO) << " Create label+property index on :" + << delta.operation_label_property.label << " (" + << delta.operation_label_property.property << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + if (!storage_->CreateIndex( + storage_->NameToLabel(delta.operation_label_property.label), + storage_->NameToProperty( + delta.operation_label_property.property), + timestamp)) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { + DLOG(INFO) << " Drop label+property index on :" + << delta.operation_label_property.label << " (" + << delta.operation_label_property.property << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + if (!storage_->DropIndex( + storage_->NameToLabel(delta.operation_label_property.label), + storage_->NameToProperty( + delta.operation_label_property.property), + timestamp)) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { + DLOG(INFO) << " Create existence constraint on :" + << delta.operation_label_property.label << " (" + << delta.operation_label_property.property << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + auto ret = storage_->CreateExistenceConstraint( + storage_->NameToLabel(delta.operation_label_property.label), + storage_->NameToProperty(delta.operation_label_property.property), + timestamp); + if (!ret.HasValue() || !ret.GetValue()) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { + DLOG(INFO) << " Drop existence constraint on :" + << delta.operation_label_property.label << " (" + << delta.operation_label_property.property << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + if (!storage_->DropExistenceConstraint( + storage_->NameToLabel(delta.operation_label_property.label), + storage_->NameToProperty( + delta.operation_label_property.property), + timestamp)) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { + std::stringstream ss; + utils::PrintIterable(ss, delta.operation_label_properties.properties); + DLOG(INFO) << " Create unique constraint on :" + << delta.operation_label_properties.label << " (" << ss.str() + << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + std::set properties; + for (const auto &prop : delta.operation_label_properties.properties) { + properties.emplace(storage_->NameToProperty(prop)); + } + auto ret = storage_->CreateUniqueConstraint( + storage_->NameToLabel(delta.operation_label_properties.label), + properties, timestamp); + if (!ret.HasValue() || + ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) + throw utils::BasicException("Invalid transaction!"); + break; + } + case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { + std::stringstream ss; + utils::PrintIterable(ss, delta.operation_label_properties.properties); + DLOG(INFO) << " Drop unique constraint on :" + << delta.operation_label_properties.label << " (" << ss.str() + << ")"; + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid transaction!"); + std::set properties; + for (const auto &prop : delta.operation_label_properties.properties) { + properties.emplace(storage_->NameToProperty(prop)); + } + auto ret = storage_->DropUniqueConstraint( + storage_->NameToLabel(delta.operation_label_properties.label), + properties, timestamp); + if (ret != UniqueConstraints::DeletionStatus::SUCCESS) + throw utils::BasicException("Invalid transaction!"); + break; + } + } + transaction_complete = + durability::IsWalDeltaDataTypeTransactionEnd(delta.type); + } + + if (commit_timestamp_and_accessor) + throw utils::BasicException("Invalid data!"); + + AppendDeltasRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); +} + +void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, + slk::Builder *res_builder) { + SnapshotReq req; + slk::Load(&req, req_reader); + + replication::Decoder decoder(req_reader); + + utils::EnsureDirOrDie(storage_->snapshot_directory_); + + const auto maybe_snapshot_path = + decoder.ReadFile(storage_->snapshot_directory_); + CHECK(maybe_snapshot_path) << "Failed to load snapshot!"; + DLOG(INFO) << "Received snapshot saved to " << *maybe_snapshot_path; + + std::unique_lock storage_guard(storage_->main_lock_); + // Clear the database + storage_->vertices_.clear(); + storage_->edges_.clear(); + + storage_->constraints_ = Constraints(); + // TODO (antonio2368): Check if there's a less hacky way + storage_->indices_.label_index = LabelIndex( + &storage_->indices_, &storage_->constraints_, storage_->config_.items); + storage_->indices_.label_property_index = LabelPropertyIndex( + &storage_->indices_, &storage_->constraints_, storage_->config_.items); + try { + DLOG(INFO) << "Loading snapshot"; + auto recovered_snapshot = durability::LoadSnapshot( + *maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, + &storage_->epoch_history_, &storage_->name_id_mapper_, + &storage_->edge_count_, storage_->config_.items); + DLOG(INFO) << "Snapshot loaded successfully"; + // If this step is present it should always be the first step of + // the recovery so we use the UUID we read from snasphost + storage_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid); + storage_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id); + const auto &recovery_info = recovered_snapshot.recovery_info; + storage_->vertex_id_ = recovery_info.next_vertex_id; + storage_->edge_id_ = recovery_info.next_edge_id; + storage_->timestamp_ = + std::max(storage_->timestamp_, recovery_info.next_timestamp); + + durability::RecoverIndicesAndConstraints( + recovered_snapshot.indices_constraints, &storage_->indices_, + &storage_->constraints_, &storage_->vertices_); + } catch (const durability::RecoveryFailure &e) { + // TODO (antonio2368): What to do if the sent snapshot is invalid + LOG(WARNING) << "Couldn't load the snapshot because of: " << e.what(); + } + storage_->last_commit_timestamp_ = storage_->timestamp_ - 1; + storage_guard.unlock(); + + SnapshotRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); + + // Delete other durability files + auto snapshot_files = durability::GetSnapshotFiles( + storage_->snapshot_directory_, storage_->uuid_); + for (const auto &[path, uuid, _] : snapshot_files) { + if (path != *maybe_snapshot_path) { + storage_->file_retainer_.DeleteFile(path); + } + } + + auto wal_files = + durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_); + if (wal_files) { + for (const auto &wal_file : *wal_files) { + storage_->file_retainer_.DeleteFile(wal_file.path); + } + + storage_->wal_file_.reset(); + } +} + +void Storage::ReplicationServer::OnlySnapshotHandler( + slk::Reader *req_reader, slk::Builder *res_builder) { + OnlySnapshotReq req; + slk::Load(&req, req_reader); + + CHECK(storage_->last_commit_timestamp_.load() < req.snapshot_timestamp) + << "Invalid snapshot timestamp, it should be less than the last" + "commited timestamp"; + + replication::Decoder decoder{req_reader}; + auto maybe_epoch_id = decoder.ReadString(); + CHECK(maybe_epoch_id) << "Invalid replication message"; + + if (*maybe_epoch_id != storage_->epoch_id_) { + storage_->epoch_history_.emplace_back(std::move(storage_->epoch_id_), + storage_->last_commit_timestamp_); + storage_->epoch_id_ = std::move(*maybe_epoch_id); + } + + storage_->last_commit_timestamp_.store(req.snapshot_timestamp); + + OnlySnapshotRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); +} + +void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, + slk::Builder *res_builder) { + WalFilesReq req; + slk::Load(&req, req_reader); + + const auto wal_file_number = req.file_number; + DLOG(INFO) << "Received WAL files: " << wal_file_number; + + replication::Decoder decoder(req_reader); + + utils::EnsureDirOrDie(storage_->wal_directory_); + + std::unique_lock storage_guard(storage_->main_lock_); + durability::RecoveredIndicesAndConstraints indices_constraints; + auto [wal_info, path] = LoadWal(&decoder, &indices_constraints); + if (wal_info.seq_num == 0) { + storage_->uuid_ = wal_info.uuid; + } + + // Check the seq number of the first wal file to see if it's the + // finalized form of the current wal on replica + if (storage_->wal_file_) { + if (storage_->wal_file_->SequenceNumber() == wal_info.seq_num && + storage_->wal_file_->Path() != path) { + storage_->wal_file_->DeleteWal(); + } + storage_->wal_file_.reset(); + } + + for (auto i = 1; i < wal_file_number; ++i) { + LoadWal(&decoder, &indices_constraints); + } + + durability::RecoverIndicesAndConstraints( + indices_constraints, &storage_->indices_, &storage_->constraints_, + &storage_->vertices_); + storage_guard.unlock(); + + WalFilesRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); +} + +void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, + slk::Builder *res_builder) { + CurrentWalReq req; + slk::Load(&req, req_reader); + + replication::Decoder decoder(req_reader); + + utils::EnsureDirOrDie(storage_->wal_directory_); + + std::unique_lock storage_guard(storage_->main_lock_); + durability::RecoveredIndicesAndConstraints indices_constraints; + auto [wal_info, path] = LoadWal(&decoder, &indices_constraints); + if (wal_info.seq_num == 0) { + storage_->uuid_ = wal_info.uuid; + } + + if (storage_->wal_file_ && + storage_->wal_file_->SequenceNumber() == wal_info.seq_num && + storage_->wal_file_->Path() != path) { + // Delete the old wal file + storage_->file_retainer_.DeleteFile(storage_->wal_file_->Path()); + } + CHECK(storage_->config_.durability.snapshot_wal_mode == + Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL); + storage_->wal_file_.emplace(std::move(path), storage_->config_.items, + &storage_->name_id_mapper_, wal_info.seq_num, + wal_info.from_timestamp, wal_info.to_timestamp, + wal_info.num_deltas, &storage_->file_retainer_); + durability::RecoverIndicesAndConstraints( + indices_constraints, &storage_->indices_, &storage_->constraints_, + &storage_->vertices_); + storage_guard.unlock(); + + CurrentWalRes res{true, storage_->last_commit_timestamp_.load()}; + slk::Save(res, res_builder); +} + +std::pair +Storage::ReplicationServer::LoadWal( + replication::Decoder *decoder, + durability::RecoveredIndicesAndConstraints *indices_constraints) { + auto maybe_wal_path = decoder->ReadFile(storage_->wal_directory_, "_MAIN"); + CHECK(maybe_wal_path) << "Failed to load WAL!"; + DLOG(INFO) << "Received WAL saved to " << *maybe_wal_path; + try { + auto wal_info = durability::ReadWalInfo(*maybe_wal_path); + if (wal_info.epoch_id != storage_->epoch_id_) { + storage_->epoch_history_.emplace_back(wal_info.epoch_id, + storage_->last_commit_timestamp_); + storage_->epoch_id_ = std::move(wal_info.epoch_id); + } + auto info = durability::LoadWal( + *maybe_wal_path, indices_constraints, storage_->last_commit_timestamp_, + &storage_->vertices_, &storage_->edges_, &storage_->name_id_mapper_, + &storage_->edge_count_, storage_->config_.items); + storage_->vertex_id_ = + std::max(storage_->vertex_id_.load(), info.next_vertex_id); + storage_->edge_id_ = std::max(storage_->edge_id_.load(), info.next_edge_id); + storage_->timestamp_ = std::max(storage_->timestamp_, info.next_timestamp); + if (info.next_timestamp != 0) { + storage_->last_commit_timestamp_ = info.next_timestamp - 1; + } + DLOG(INFO) << *maybe_wal_path << " loaded successfully"; + return {std::move(wal_info), std::move(*maybe_wal_path)}; + } catch (const durability::RecoveryFailure &e) { + LOG(FATAL) << "Couldn't recover WAL deltas from " << *maybe_wal_path + << " because of: " << e.what(); + } +} + +Storage::ReplicationServer::~ReplicationServer() { + if (rpc_server_) { + rpc_server_->Shutdown(); + rpc_server_->AwaitShutdown(); + } +} +} // namespace storage diff --git a/src/storage/v2/replication/replication_server.hpp b/src/storage/v2/replication/replication_server.hpp new file mode 100644 index 000000000..c8cb9e093 --- /dev/null +++ b/src/storage/v2/replication/replication_server.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "storage/v2/storage.hpp" + +namespace storage { + +class Storage::ReplicationServer { + public: + explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint); + ReplicationServer(const ReplicationServer &) = delete; + ReplicationServer(ReplicationServer &&) = delete; + ReplicationServer &operator=(const ReplicationServer &) = delete; + ReplicationServer &operator=(ReplicationServer &&) = delete; + + ~ReplicationServer(); + + private: + // RPC handlers + void HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder); + void AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder); + void SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder); + // RPC for replicating only the commit of the last snapshot as that is the + // only difference between the replica and main (all of the data is + // already replicated through previous WAL\Snapshot files) + void OnlySnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder); + void WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder); + void CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder); + + std::pair LoadWal( + replication::Decoder *decoder, + durability::RecoveredIndicesAndConstraints *indices_constraints); + + std::optional rpc_server_context_; + std::optional rpc_server_; + + Storage *storage_; +}; + +} // namespace storage diff --git a/src/storage/v2/replication/rpc.lcp b/src/storage/v2/replication/rpc.lcp index 00c672100..8dec6807a 100644 --- a/src/storage/v2/replication/rpc.lcp +++ b/src/storage/v2/replication/rpc.lcp @@ -23,9 +23,10 @@ cpp<# (current-commit-timestamp :uint64_t)))) (lcp:define-rpc heartbeat - (:request ()) + (:request ((main-commit-timestamp :uint64_t))) (:response - ((current-commit-timestamp :uint64_t)))) + ((success :bool) + (current-commit-timestamp :uint64_t)))) (lcp:define-rpc snapshot (:request ()) diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 8e119447c..db4b5d777 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -24,6 +24,8 @@ #include "utils/uuid.hpp" #ifdef MG_ENTERPRISE +#include "storage/v2/replication/replication_client.hpp" +#include "storage/v2/replication/replication_server.hpp" #include "storage/v2/replication/rpc.hpp" #endif @@ -35,6 +37,10 @@ DEFINE_bool(async_replica, false, "Set to true to be the replica"); namespace storage { +namespace { +constexpr uint16_t kEpochHistoryRetention = 1000; +} // namespace + auto AdvanceToVisibleVertex(utils::SkipList::Iterator it, utils::SkipList::Iterator end, std::optional *vertex, @@ -323,7 +329,8 @@ Storage::Storage(Config config) durability::kWalDirectory), lock_file_path_(config_.durability.storage_directory / durability::kLockFile), - uuid_(utils::GenerateUUID()) { + uuid_(utils::GenerateUUID()), + epoch_id_(utils::GenerateUUID()) { if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || config_.durability.snapshot_on_exit || @@ -355,9 +362,9 @@ Storage::Storage(Config config) } if (config_.durability.recover_on_startup) { auto info = durability::RecoverData( - snapshot_directory_, wal_directory_, &uuid_, &vertices_, &edges_, - &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, - &wal_seq_num_); + snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, + &epoch_history_, &vertices_, &edges_, &edge_count_, &name_id_mapper_, + &indices_, &constraints_, config_.items, &wal_seq_num_); if (info) { vertex_id_ = info->next_vertex_id; edge_id_ = info->next_edge_id; @@ -418,7 +425,6 @@ Storage::Storage(Config config) // For testing purposes until we can define the instance type from // a query. if (FLAGS_main) { - SetReplicationRole(); RegisterReplica("REPLICA_SYNC", io::network::Endpoint{"127.0.0.1", 10000}); RegisterReplica("REPLICA_ASYNC", io::network::Endpoint{"127.0.0.1", 10002}); } else if (FLAGS_replica) { @@ -1661,8 +1667,8 @@ bool Storage::InitializeWalFile() { Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) return false; if (!wal_file_) { - wal_file_.emplace(wal_directory_, uuid_, config_.items, &name_id_mapper_, - wal_seq_num_++, &file_retainer_); + wal_file_.emplace(wal_directory_, uuid_, epoch_id_, config_.items, + &name_id_mapper_, wal_seq_num_++, &file_retainer_); } return true; } @@ -1697,13 +1703,10 @@ void Storage::AppendToWal(const Transaction &transaction, transaction.commit_timestamp->load(std::memory_order_acquire); #ifdef MG_ENTERPRISE - // We need to keep this lock because handler takes a pointer to the client - // from which it was created - std::shared_lock replication_guard(replication_lock_); if (replication_role_.load() == ReplicationRole::MAIN) { replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { - client.StartTransactionReplication(wal_file_->SequenceNumber()); + client->StartTransactionReplication(wal_file_->SequenceNumber()); } }); } @@ -1727,7 +1730,7 @@ void Storage::AppendToWal(const Transaction &transaction, #ifdef MG_ENTERPRISE replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { - client.IfStreamingTransaction([&](auto &stream) { + client->IfStreamingTransaction([&](auto &stream) { stream.AppendDelta(*delta, parent, final_commit_timestamp); }); } @@ -1869,10 +1872,10 @@ void Storage::AppendToWal(const Transaction &transaction, #ifdef MG_ENTERPRISE replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { - client.IfStreamingTransaction([&](auto &stream) { + client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); }); - client.FinalizeTransactionReplication(); + client->FinalizeTransactionReplication(); } }); #endif @@ -1886,16 +1889,15 @@ void Storage::AppendToWal(durability::StorageGlobalOperation operation, final_commit_timestamp); #ifdef MG_ENTERPRISE { - std::shared_lock replication_guard(replication_lock_); if (replication_role_.load() == ReplicationRole::MAIN) { replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { - client.StartTransactionReplication(wal_file_->SequenceNumber()); - client.IfStreamingTransaction([&](auto &stream) { + client->StartTransactionReplication(wal_file_->SequenceNumber()); + client->IfStreamingTransaction([&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); }); - client.FinalizeTransactionReplication(); + client->FinalizeTransactionReplication(); } }); } @@ -1922,8 +1924,8 @@ void Storage::CreateSnapshot() { durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, config_.durability.snapshot_retention_count, &vertices_, &edges_, &name_id_mapper_, &indices_, - &constraints_, config_.items, uuid_, - &file_retainer_); + &constraints_, config_.items, uuid_, epoch_id_, + epoch_history_, &file_retainer_); // Finalize snapshot transaction. commit_log_.MarkFinished(transaction.start_timestamp); @@ -1945,678 +1947,71 @@ uint64_t Storage::CommitTimestamp( } #ifdef MG_ENTERPRISE -std::pair Storage::LoadWal( - replication::Decoder *decoder, - durability::RecoveredIndicesAndConstraints *indices_constraints) { - auto maybe_wal_path = decoder->ReadFile(wal_directory_, "_MAIN"); - CHECK(maybe_wal_path) << "Failed to load WAL!"; - DLOG(INFO) << "Received WAL saved to " << *maybe_wal_path; - try { - auto wal_info = durability::ReadWalInfo(*maybe_wal_path); - auto info = durability::LoadWal( - *maybe_wal_path, indices_constraints, last_commit_timestamp_, - &vertices_, &edges_, &name_id_mapper_, &edge_count_, config_.items); - vertex_id_ = std::max(vertex_id_.load(), info.next_vertex_id); - edge_id_ = std::max(edge_id_.load(), info.next_edge_id); - timestamp_ = std::max(timestamp_, info.next_timestamp); - if (info.next_timestamp != 0) { - last_commit_timestamp_ = info.next_timestamp - 1; - } - DLOG(INFO) << *maybe_wal_path << " loaded successfully"; - return {std::move(wal_info), std::move(*maybe_wal_path)}; - } catch (const durability::RecoveryFailure &e) { - LOG(FATAL) << "Couldn't recover WAL deltas from " << *maybe_wal_path - << " because of: " << e.what(); - } +void Storage::ConfigureReplica(io::network::Endpoint endpoint) { + replication_server_ = + std::make_unique(this, std::move(endpoint)); } -void Storage::ConfigureReplica(io::network::Endpoint endpoint) { - replication_server_.emplace(); +void Storage::ConfigureMain() { + // Main instance does not need replication server + // This should be always called first so we finalize everything + replication_server_.reset(nullptr); - // Create RPC server. - // TODO (antonio2368): Add support for SSL. - replication_server_->rpc_server_context.emplace(); - // NOTE: The replication server must have a single thread for processing - // because there is no need for more processing threads - each replica can - // have only a single main server. Also, the single-threaded guarantee - // simplifies the rest of the implementation. - replication_server_->rpc_server.emplace( - endpoint, &*replication_server_->rpc_server_context, - /* workers_count = */ 1); + std::unique_lock engine_guard{engine_lock_}; + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_.reset(); + } - replication_server_->rpc_server->Register( - [this](auto *req_reader, auto *res_builder) { - HeartbeatReq req; - slk::Load(&req, req_reader); - - DLOG(INFO) << "Received HeartbeatRpc:"; - - HeartbeatRes res{last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - }); - replication_server_->rpc_server->Register< - AppendDeltasRpc>([this](auto *req_reader, auto *res_builder) { - AppendDeltasReq req; - slk::Load(&req, req_reader); - - DLOG(INFO) << "Received AppendDeltasRpc:"; - - constexpr auto is_transaction_complete = - [](const durability::WalDeltaData::Type delta_type) { - switch (delta_type) { - case durability::WalDeltaData::Type::TRANSACTION_END: - case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: - case durability::WalDeltaData::Type::LABEL_INDEX_DROP: - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: - return true; - default: - return false; - } - }; - - replication::Decoder decoder(req_reader); - const auto read_delta = - [&]() -> std::pair { - try { - auto timestamp = ReadWalDeltaHeader(&decoder); - DLOG(INFO) << " Timestamp " << timestamp; - auto delta = ReadWalDeltaData(&decoder); - return {timestamp, delta}; - } catch (const slk::SlkReaderException &) { - throw utils::BasicException("Missing data!"); - } catch (const durability::RecoveryFailure &) { - throw utils::BasicException("Invalid data!"); - } - }; - - if (req.previous_commit_timestamp != last_commit_timestamp_.load()) { - // Empty the stream - bool transaction_complete = false; - while (!transaction_complete) { - DLOG(INFO) << "Skipping delta"; - const auto [timestamp, delta] = read_delta(); - transaction_complete = is_transaction_complete(delta.type); - } - - AppendDeltasRes res{false, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - return; - } - - if (wal_file_) { - if (req.seq_num > wal_file_->SequenceNumber()) { - wal_file_->FinalizeWal(); - wal_file_.reset(); - wal_seq_num_ = req.seq_num; - } else { - CHECK(wal_file_->SequenceNumber() == req.seq_num) - << "Invalid sequence number of current wal file"; - wal_seq_num_ = req.seq_num + 1; - } - } else { - wal_seq_num_ = req.seq_num; - } - - auto edge_acc = edges_.access(); - auto vertex_acc = vertices_.access(); - - std::optional> - commit_timestamp_and_accessor; - auto get_transaction = - [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { - if (!commit_timestamp_and_accessor) { - commit_timestamp_and_accessor.emplace(commit_timestamp, Access()); - } else if (commit_timestamp_and_accessor->first != commit_timestamp) { - throw utils::BasicException("Received more than one transaction!"); - } - return &commit_timestamp_and_accessor->second; - }; - - bool transaction_complete = false; - for (uint64_t i = 0; !transaction_complete; ++i) { - DLOG(INFO) << " Delta " << i; - const auto [timestamp, delta] = read_delta(); - - switch (delta.type) { - case durability::WalDeltaData::Type::VERTEX_CREATE: { - DLOG(INFO) << " Create vertex " - << delta.vertex_create_delete.gid.AsUint(); - auto transaction = get_transaction(timestamp); - transaction->CreateVertex(delta.vertex_create_delete.gid); - break; - } - case durability::WalDeltaData::Type::VERTEX_DELETE: { - DLOG(INFO) << " Delete vertex " - << delta.vertex_create_delete.gid.AsUint(); - auto transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, - storage::View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = transaction->DeleteVertex(&*vertex); - if (ret.HasError() || !ret.GetValue()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: { - DLOG(INFO) << " Vertex " - << delta.vertex_add_remove_label.gid.AsUint() - << " add label " << delta.vertex_add_remove_label.label; - auto transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex( - delta.vertex_add_remove_label.gid, storage::View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = vertex->AddLabel( - transaction->NameToLabel(delta.vertex_add_remove_label.label)); - if (ret.HasError() || !ret.GetValue()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: { - DLOG(INFO) << " Vertex " - << delta.vertex_add_remove_label.gid.AsUint() - << " remove label " << delta.vertex_add_remove_label.label; - auto transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex( - delta.vertex_add_remove_label.gid, storage::View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = vertex->RemoveLabel( - transaction->NameToLabel(delta.vertex_add_remove_label.label)); - if (ret.HasError() || !ret.GetValue()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: { - DLOG(INFO) << " Vertex " - << delta.vertex_edge_set_property.gid.AsUint() - << " set property " - << delta.vertex_edge_set_property.property << " to " - << delta.vertex_edge_set_property.value; - auto transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex( - delta.vertex_edge_set_property.gid, storage::View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = - vertex->SetProperty(transaction->NameToProperty( - delta.vertex_edge_set_property.property), - delta.vertex_edge_set_property.value); - if (ret.HasError()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_CREATE: { - DLOG(INFO) << " Create edge " - << delta.edge_create_delete.gid.AsUint() << " of type " - << delta.edge_create_delete.edge_type << " from vertex " - << delta.edge_create_delete.from_vertex.AsUint() - << " to vertex " - << delta.edge_create_delete.to_vertex.AsUint(); - auto transaction = get_transaction(timestamp); - auto from_vertex = transaction->FindVertex( - delta.edge_create_delete.from_vertex, storage::View::NEW); - if (!from_vertex) throw utils::BasicException("Invalid transaction!"); - auto to_vertex = transaction->FindVertex( - delta.edge_create_delete.to_vertex, storage::View::NEW); - if (!to_vertex) throw utils::BasicException("Invalid transaction!"); - auto edge = transaction->CreateEdge( - &*from_vertex, &*to_vertex, - transaction->NameToEdgeType(delta.edge_create_delete.edge_type), - delta.edge_create_delete.gid); - if (edge.HasError()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_DELETE: { - DLOG(INFO) << " Delete edge " - << delta.edge_create_delete.gid.AsUint() << " of type " - << delta.edge_create_delete.edge_type << " from vertex " - << delta.edge_create_delete.from_vertex.AsUint() - << " to vertex " - << delta.edge_create_delete.to_vertex.AsUint(); - auto transaction = get_transaction(timestamp); - auto from_vertex = transaction->FindVertex( - delta.edge_create_delete.from_vertex, storage::View::NEW); - if (!from_vertex) throw utils::BasicException("Invalid transaction!"); - auto to_vertex = transaction->FindVertex( - delta.edge_create_delete.to_vertex, storage::View::NEW); - if (!to_vertex) throw utils::BasicException("Invalid transaction!"); - auto edges = from_vertex->OutEdges( - storage::View::NEW, - {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)}, - &*to_vertex); - if (edges.HasError()) - throw utils::BasicException("Invalid transaction!"); - if (edges->size() != 1) - throw utils::BasicException("Invalid transaction!"); - auto &edge = (*edges)[0]; - auto ret = transaction->DeleteEdge(&edge); - if (ret.HasError()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: { - DLOG(INFO) << " Edge " - << delta.vertex_edge_set_property.gid.AsUint() - << " set property " - << delta.vertex_edge_set_property.property << " to " - << delta.vertex_edge_set_property.value; - - if (!config_.items.properties_on_edges) - throw utils::BasicException( - "Can't set properties on edges because properties on edges " - "are disabled!"); - - auto transaction = get_transaction(timestamp); - - // The following block of code effectively implements `FindEdge` and - // yields an accessor that is only valid for managing the edge's - // properties. - auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); - if (edge == edge_acc.end()) - throw utils::BasicException("Invalid transaction!"); - // The edge visibility check must be done here manually because we - // don't allow direct access to the edges through the public API. - { - bool is_visible = true; - Delta *delta = nullptr; - { - std::lock_guard guard(edge->lock); - is_visible = !edge->deleted; - delta = edge->delta; - } - ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, - [&is_visible](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: { - is_visible = true; - break; - } - case Delta::Action::DELETE_OBJECT: { - is_visible = false; - break; - } - } - }); - if (!is_visible) - throw utils::BasicException("Invalid transaction!"); - } - EdgeRef edge_ref(&*edge); - // Here we create an edge accessor that we will use to get the - // properties of the edge. The accessor is created with an invalid - // type and invalid from/to pointers because we don't know them - // here, but that isn't an issue because we won't use that part of - // the API here. - auto ea = EdgeAccessor{edge_ref, - EdgeTypeId::FromUint(0UL), - nullptr, - nullptr, - &transaction->transaction_, - &indices_, - &constraints_, - config_.items}; - - auto ret = - ea.SetProperty(transaction->NameToProperty( - delta.vertex_edge_set_property.property), - delta.vertex_edge_set_property.value); - if (ret.HasError()) - throw utils::BasicException("Invalid transaction!"); - break; - } - - case durability::WalDeltaData::Type::TRANSACTION_END: { - DLOG(INFO) << " Transaction end"; - if (!commit_timestamp_and_accessor || - commit_timestamp_and_accessor->first != timestamp) - throw utils::BasicException("Invalid data!"); - auto ret = commit_timestamp_and_accessor->second.Commit( - commit_timestamp_and_accessor->first); - if (ret.HasError()) - throw utils::BasicException("Invalid transaction!"); - commit_timestamp_and_accessor = std::nullopt; - break; - } - - case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: { - DLOG(INFO) << " Create label index on :" - << delta.operation_label.label; - // Need to send the timestamp - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - if (!CreateIndex(NameToLabel(delta.operation_label.label), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_INDEX_DROP: { - DLOG(INFO) << " Drop label index on :" - << delta.operation_label.label; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - if (!DropIndex(NameToLabel(delta.operation_label.label), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { - DLOG(INFO) << " Create label+property index on :" - << delta.operation_label_property.label << " (" - << delta.operation_label_property.property << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - if (!CreateIndex( - NameToLabel(delta.operation_label_property.label), - NameToProperty(delta.operation_label_property.property), - timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { - DLOG(INFO) << " Drop label+property index on :" - << delta.operation_label_property.label << " (" - << delta.operation_label_property.property << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - if (!DropIndex( - NameToLabel(delta.operation_label_property.label), - NameToProperty(delta.operation_label_property.property), - timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { - DLOG(INFO) << " Create existence constraint on :" - << delta.operation_label_property.label << " (" - << delta.operation_label_property.property << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - auto ret = CreateExistenceConstraint( - NameToLabel(delta.operation_label_property.label), - NameToProperty(delta.operation_label_property.property), - timestamp); - if (!ret.HasValue() || !ret.GetValue()) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { - DLOG(INFO) << " Drop existence constraint on :" - << delta.operation_label_property.label << " (" - << delta.operation_label_property.property << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - if (!DropExistenceConstraint( - NameToLabel(delta.operation_label_property.label), - NameToProperty(delta.operation_label_property.property), - timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { - std::stringstream ss; - utils::PrintIterable(ss, delta.operation_label_properties.properties); - DLOG(INFO) << " Create unique constraint on :" - << delta.operation_label_properties.label << " (" - << ss.str() << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - std::set properties; - for (const auto &prop : delta.operation_label_properties.properties) { - properties.emplace(NameToProperty(prop)); - } - auto ret = CreateUniqueConstraint( - NameToLabel(delta.operation_label_properties.label), properties, - timestamp); - if (!ret.HasValue() || - ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { - std::stringstream ss; - utils::PrintIterable(ss, delta.operation_label_properties.properties); - DLOG(INFO) << " Drop unique constraint on :" - << delta.operation_label_properties.label << " (" - << ss.str() << ")"; - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid transaction!"); - std::set properties; - for (const auto &prop : delta.operation_label_properties.properties) { - properties.emplace(NameToProperty(prop)); - } - auto ret = DropUniqueConstraint( - NameToLabel(delta.operation_label_properties.label), properties, - timestamp); - if (ret != UniqueConstraints::DeletionStatus::SUCCESS) - throw utils::BasicException("Invalid transaction!"); - break; - } - } - transaction_complete = is_transaction_complete(delta.type); - } - - if (commit_timestamp_and_accessor) - throw utils::BasicException("Invalid data!"); - - AppendDeltasRes res{true, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - }); - replication_server_->rpc_server->Register( - [this](auto *req_reader, auto *res_builder) { - DLOG(INFO) << "Received SnapshotRpc"; - SnapshotReq req; - slk::Load(&req, req_reader); - - replication::Decoder decoder(req_reader); - - utils::EnsureDirOrDie(snapshot_directory_); - - const auto maybe_snapshot_path = decoder.ReadFile(snapshot_directory_); - CHECK(maybe_snapshot_path) << "Failed to load snapshot!"; - DLOG(INFO) << "Received snapshot saved to " << *maybe_snapshot_path; - - std::unique_lock storage_guard(main_lock_); - // Clear the database - vertices_.clear(); - edges_.clear(); - - constraints_ = Constraints(); - // TODO (antonio2368): Check if there's a less hacky way - indices_.label_index = - LabelIndex(&indices_, &constraints_, config_.items); - indices_.label_property_index = - LabelPropertyIndex(&indices_, &constraints_, config_.items); - try { - DLOG(INFO) << "Loading snapshot"; - auto recovered_snapshot = durability::LoadSnapshot( - *maybe_snapshot_path, &vertices_, &edges_, &name_id_mapper_, - &edge_count_, config_.items); - DLOG(INFO) << "Snapshot loaded successfully"; - // If this step is present it should always be the first step of - // the recovery so we use the UUID we read from snasphost - uuid_ = recovered_snapshot.snapshot_info.uuid; - const auto &recovery_info = recovered_snapshot.recovery_info; - vertex_id_ = recovery_info.next_vertex_id; - edge_id_ = recovery_info.next_edge_id; - timestamp_ = std::max(timestamp_, recovery_info.next_timestamp); - - durability::RecoverIndicesAndConstraints( - recovered_snapshot.indices_constraints, &indices_, &constraints_, - &vertices_); - } catch (const durability::RecoveryFailure &e) { - // TODO (antonio2368): What to do if the sent snapshot is invalid - LOG(WARNING) << "Couldn't load the snapshot because of: " << e.what(); - } - last_commit_timestamp_ = timestamp_ - 1; - storage_guard.unlock(); - - SnapshotRes res{true, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - - // Delete other durability files - auto snapshot_files = - durability::GetSnapshotFiles(snapshot_directory_, uuid_); - for (const auto &[path, uuid, _] : snapshot_files) { - if (path != *maybe_snapshot_path) { - file_retainer_.DeleteFile(path); - } - } - - auto wal_files = durability::GetWalFiles(wal_directory_, uuid_); - if (wal_files) { - for (const auto &[seq_num, from_timestamp, to_timestamp, _, path] : - *wal_files) { - file_retainer_.DeleteFile(path); - } - - wal_file_.reset(); - } - }); - replication_server_->rpc_server->Register( - [this](auto *req_reader, auto *res_builder) { - DLOG(INFO) << "Received OnlySnapshotRpc"; - OnlySnapshotReq req; - slk::Load(&req, req_reader); - - CHECK(last_commit_timestamp_.load() < req.snapshot_timestamp) - << "Invalid snapshot timestamp, it should be less than the last" - "commited timestamp"; - - last_commit_timestamp_.store(req.snapshot_timestamp); - - SnapshotRes res{true, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - }); - replication_server_->rpc_server->Register( - [this](auto *req_reader, auto *res_builder) { - DLOG(INFO) << "Received WalFilesRpc"; - WalFilesReq req; - slk::Load(&req, req_reader); - - const auto wal_file_number = req.file_number; - DLOG(INFO) << "Received WAL files: " << wal_file_number; - - replication::Decoder decoder(req_reader); - - utils::EnsureDirOrDie(wal_directory_); - - std::unique_lock storage_guard(main_lock_); - durability::RecoveredIndicesAndConstraints indices_constraints; - auto [wal_info, path] = LoadWal(&decoder, &indices_constraints); - if (wal_info.seq_num == 0) { - uuid_ = wal_info.uuid; - } - // Check the seq number of the first wal file to see if it's the - // finalized form of the current wal on replica - if (wal_file_) { - if (wal_file_->SequenceNumber() == wal_info.seq_num && - wal_file_->Path() != path) { - wal_file_->DeleteWal(); - } - wal_file_.reset(); - } - - for (auto i = 1; i < wal_file_number; ++i) { - LoadWal(&decoder, &indices_constraints); - } - - durability::RecoverIndicesAndConstraints(indices_constraints, &indices_, - &constraints_, &vertices_); - storage_guard.unlock(); - - WalFilesRes res{true, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - }); - replication_server_->rpc_server->Register( - [this](auto *req_reader, auto *res_builder) { - DLOG(INFO) << "Received CurrentWalRpc"; - CurrentWalReq req; - slk::Load(&req, req_reader); - - replication::Decoder decoder(req_reader); - - utils::EnsureDirOrDie(wal_directory_); - - std::unique_lock storage_guard(main_lock_); - durability::RecoveredIndicesAndConstraints indices_constraints; - auto [wal_info, path] = LoadWal(&decoder, &indices_constraints); - if (wal_info.seq_num == 0) { - uuid_ = wal_info.uuid; - } - - if (wal_file_ && wal_file_->SequenceNumber() == wal_info.seq_num && - wal_file_->Path() != path) { - // Delete the old wal file - file_retainer_.DeleteFile(wal_file_->Path()); - } - CHECK(config_.durability.snapshot_wal_mode == - Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL); - wal_file_.emplace(std::move(path), config_.items, &name_id_mapper_, - wal_info.seq_num, wal_info.from_timestamp, - wal_info.to_timestamp, wal_info.num_deltas, - &file_retainer_); - durability::RecoverIndicesAndConstraints(indices_constraints, &indices_, - &constraints_, &vertices_); - storage_guard.unlock(); - - CurrentWalRes res{true, last_commit_timestamp_.load()}; - slk::Save(res, res_builder); - }); - replication_server_->rpc_server->Start(); + // Generate new epoch id and save the last one to the history. + if (epoch_history_.size() == kEpochHistoryRetention) { + epoch_history_.pop_front(); + } + epoch_history_.emplace_back(std::move(epoch_id_), last_commit_timestamp_); + epoch_id_ = utils::GenerateUUID(); } void Storage::RegisterReplica( std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode) { - std::shared_lock guard(replication_lock_); // TODO (antonio2368): This shouldn't stop the main instance CHECK(replication_role_.load() == ReplicationRole::MAIN) << "Only main instance can register a replica!"; - // We can safely add new elements to the list because it doesn't validate - // existing references/iteratos replication_clients_.WithLock([&](auto &clients) { if (std::any_of(clients.begin(), clients.end(), - [&](auto &client) { return client.Name() == name; })) { + [&](auto &client) { return client->Name() == name; })) { throw utils::BasicException("Replica with a same name already exists!"); } - clients.emplace_back(std::move(name), last_commit_timestamp_, - &name_id_mapper_, config_.items, &file_retainer_, - snapshot_directory_, wal_directory_, uuid_, &wal_file_, - &engine_lock_, endpoint, false, replication_mode); }); + + auto client = std::make_unique( + std::move(name), this, endpoint, false, replication_mode); + + replication_clients_.WithLock( + [&](auto &clients) { clients.push_back(std::move(client)); }); } void Storage::UnregisterReplica(const std::string_view name) { - std::unique_lock replication_guard(replication_lock_); CHECK(replication_role_.load() == ReplicationRole::MAIN) << "Only main instance can unregister a replica!"; replication_clients_.WithLock([&](auto &clients) { - clients.remove_if( - [&](const auto &client) { return client.Name() == name; }); + std::erase_if(clients, + [&](const auto &client) { return client->Name() == name; }); }); } -std::optional Storage::ReplicaState( +std::optional Storage::GetReplicaState( const std::string_view name) { return replication_clients_.WithLock( [&](auto &clients) -> std::optional { const auto client_it = std::find_if( clients.cbegin(), clients.cend(), - [name](auto &client) { return client.Name() == name; }); + [name](auto &client) { return client->Name() == name; }); if (client_it == clients.cend()) { return std::nullopt; } - return client_it->State(); + return (*client_it)->State(); }); } #endif diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 02aaf978f..e84f28e24 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -24,10 +24,11 @@ #include "utils/scheduler.hpp" #include "utils/skip_list.hpp" #include "utils/synchronized.hpp" +#include "utils/uuid.hpp" #ifdef MG_ENTERPRISE #include "rpc/server.hpp" -#include "storage/v2/replication/replication.hpp" +#include "storage/v2/replication/enums.hpp" #include "storage/v2/replication/rpc.hpp" #include "storage/v2/replication/serialization.hpp" #endif @@ -419,13 +420,10 @@ class Storage final { return; } - std::unique_lock replication_guard(replication_lock_); - if constexpr (role == ReplicationRole::REPLICA) { ConfigureReplica(std::forward(args)...); - } else if (role == ReplicationRole::MAIN) { - // Main instance does not need replication server - replication_server_.reset(); + } else if constexpr (role == ReplicationRole::MAIN) { + ConfigureMain(std::forward(args)...); } replication_role_.store(role); @@ -436,7 +434,8 @@ class Storage final { replication::ReplicationMode::SYNC); void UnregisterReplica(std::string_view name); - std::optional ReplicaState(std::string_view name); + std::optional GetReplicaState( + std::string_view name); #endif private: @@ -462,10 +461,7 @@ class Storage final { #ifdef MG_ENTERPRISE void ConfigureReplica(io::network::Endpoint endpoint); - - std::pair LoadWal( - replication::Decoder *decoder, - durability::RecoveredIndicesAndConstraints *indices_constraints); + void ConfigureMain(); #endif // Main storage lock. @@ -538,6 +534,26 @@ class Storage final { // Sequence number used to keep track of the chain of WALs. uint64_t wal_seq_num_{0}; + // UUID to distinguish different main instance runs for replication process + // on SAME storage. + // Multiple instances can have same storage UUID and be MAIN at the same time. + // We cannot compare commit timestamps of those instances if one of them + // becomes the replica of the other so we use epoch_id_ as additional + // discriminating property. + // Example of this: + // We have 2 instances of the same storage, S1 and S2. + // S1 and S2 are MAIN and accept their own commits and write them to the WAL. + // At the moment when S1 commited a transaction with timestamp 20, and S2 + // a different transaction with timestamp 15, we change S2's role to REPLICA + // and register it on S1. + // Without using the epoch_id, we don't know that S1 and S2 have completely + // different transactions, we think that the S2 is behind only by 5 commits. + std::string epoch_id_; + // History of the previous epoch ids. + // Each value consists of the epoch id along the last commit belonging to that + // epoch. + std::deque> epoch_history_; + std::optional wal_file_; uint64_t wal_unsynced_transactions_{0}; @@ -545,32 +561,26 @@ class Storage final { // Replication #ifdef MG_ENTERPRISE + // Last commited timestamp std::atomic last_commit_timestamp_{kTimestampInitialId}; - utils::RWLock replication_lock_{utils::RWLock::Priority::WRITE}; - struct ReplicationServer { - std::optional rpc_server_context; - std::optional rpc_server; - - explicit ReplicationServer() = default; - ReplicationServer(const ReplicationServer &) = delete; - ReplicationServer(ReplicationServer &&) = delete; - ReplicationServer &operator=(const ReplicationServer &) = delete; - ReplicationServer &operator=(ReplicationServer &&) = delete; - - ~ReplicationServer() { - if (rpc_server) { - rpc_server->Shutdown(); - rpc_server->AwaitShutdown(); - } - } - }; + class ReplicationServer; + std::unique_ptr replication_server_{nullptr}; + class ReplicationClient; + // We create ReplicationClient using unique_ptr so we can move + // newly created client into the vector. + // We cannot move the client directly because it contains ThreadPool + // which cannot be moved. Also, the move is necessary because + // we don't want to create the client directly inside the vector + // because that would require the lock on the list putting all + // commits (they iterate list of clients) to halt. + // This way we can initiliaze client in main thread which means + // that we can immediately notify the user if the intiialization + // failed. using ReplicationClientList = - utils::Synchronized, + utils::Synchronized>, utils::SpinLock>; - - std::optional replication_server_; ReplicationClientList replication_clients_; std::atomic replication_role_{ReplicationRole::MAIN}; diff --git a/tests/integration/durability/runner.py b/tests/integration/durability/runner.py index 75828f166..27a7c70e4 100755 --- a/tests/integration/durability/runner.py +++ b/tests/integration/durability/runner.py @@ -15,7 +15,9 @@ TESTS_DIR = os.path.join(SCRIPT_DIR, "tests") SNAPSHOT_FILE_NAME = "snapshot.bin" WAL_FILE_NAME = "wal.bin" -DUMP_FILE_NAME = "expected.cypher" + +DUMP_SNAPSHOT_FILE_NAME = "expected_snapshot.cypher" +DUMP_WAL_FILE_NAME = "expected_wal.cypher" def wait_for_server(port, delay=0.1): @@ -38,7 +40,12 @@ def list_to_string(data): return ret -def execute_test(memgraph_binary, dump_binary, test_directory, test_type): +def execute_test( + memgraph_binary, + dump_binary, + test_directory, + test_type, + write_expected): assert test_type in ["SNAPSHOT", "WAL"], \ "Test type should be either 'SNAPSHOT' or 'WAL'." print("\033[1;36m~~ Executing test {} ({}) ~~\033[0m" @@ -82,15 +89,25 @@ def execute_test(memgraph_binary, dump_binary, test_directory, test_type): memgraph.terminate() assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!" - # Compare dump files - expected_dump_file = os.path.join(test_directory, DUMP_FILE_NAME) - assert os.path.exists(expected_dump_file), \ - "Could not find expected dump path {}".format(expected_dump_file) - queries_got = sorted_content(dump_output_file.name) - queries_expected = sorted_content(expected_dump_file) - assert queries_got == queries_expected, "Expected\n{}\nto be equal to\n" \ - "{}".format(list_to_string(queries_got), - list_to_string(queries_expected)) + dump_file_name = DUMP_SNAPSHOT_FILE_NAME if test_type == "SNAPSHOT" else DUMP_WAL_FILE_NAME + + if write_expected: + with open(dump_output_file.name, 'r') as dump: + queries_got = dump.readlines() + # Write dump files + expected_dump_file = os.path.join(test_directory, dump_file_name) + with open(expected_dump_file, 'w') as expected: + expected.writelines(queries_got) + else: + # Compare dump files + expected_dump_file = os.path.join(test_directory, dump_file_name) + assert os.path.exists(expected_dump_file), \ + "Could not find expected dump path {}".format(expected_dump_file) + queries_got = sorted_content(dump_output_file.name) + queries_expected = sorted_content(expected_dump_file) + assert queries_got == queries_expected, "Expected\n{}\nto be equal to\n" \ + "{}".format(list_to_string(queries_got), + list_to_string(queries_expected)) print("\033[1;32m~~ Test successful ~~\033[0m\n") @@ -112,9 +129,11 @@ def find_test_directories(directory): continue snapshot_file = os.path.join(test_dir_path, SNAPSHOT_FILE_NAME) wal_file = os.path.join(test_dir_path, WAL_FILE_NAME) - dump_file = os.path.join(test_dir_path, DUMP_FILE_NAME) - if (os.path.isfile(snapshot_file) and os.path.isfile(dump_file) and - os.path.isfile(wal_file)): + dump_snapshot_file = os.path.join( + test_dir_path, DUMP_SNAPSHOT_FILE_NAME) + dump_wal_file = os.path.join(test_dir_path, DUMP_WAL_FILE_NAME) + if (os.path.isfile(snapshot_file) and os.path.isfile(dump_snapshot_file) + and os.path.isfile(wal_file) and os.path.isfile(dump_wal_file)): test_dirs.append(test_dir_path) else: raise Exception("Missing data in test directory '{}'" @@ -129,13 +148,27 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--memgraph", default=memgraph_binary) parser.add_argument("--dump", default=dump_binary) + parser.add_argument( + '--write-expected', + action='store_true', + help='Overwrite the expected cypher with results from current run') args = parser.parse_args() test_directories = find_test_directories(TESTS_DIR) assert len(test_directories) > 0, "No tests have been found!" for test_directory in test_directories: - execute_test(args.memgraph, args.dump, test_directory, "SNAPSHOT") - execute_test(args.memgraph, args.dump, test_directory, "WAL") + execute_test( + args.memgraph, + args.dump, + test_directory, + "SNAPSHOT", + args.write_expected) + execute_test( + args.memgraph, + args.dump, + test_directory, + "WAL", + args.write_expected) sys.exit(0) diff --git a/tests/integration/durability/tests/v12/test_all/expected.cypher b/tests/integration/durability/tests/v12/test_all/expected.cypher deleted file mode 100644 index b69ae65eb..000000000 --- a/tests/integration/durability/tests/v12/test_all/expected.cypher +++ /dev/null @@ -1,11 +0,0 @@ -CREATE INDEX ON :`label`; -CREATE INDEX ON :`label2`(`prop2`); -CREATE INDEX ON :`label2`(`prop`); -CREATE CONSTRAINT ON (u:`label`) ASSERT EXISTS (u.`ext`); -CREATE INDEX ON :__mg_vertex__(__mg_id__); -CREATE (:__mg_vertex__:`label2` {__mg_id__: 0, `prop2`: ["kaj", 2, Null, {`prop4`: -1.341}], `prop`: "joj", `ext`: 2}); -CREATE (:__mg_vertex__:`label`:`label2` {__mg_id__: 1, `prop`: "joj", `ext`: 2}); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 0 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 1 CREATE (u)-[:`link` {`prop`: -1, `ext`: [false, {`k`: "l"}]}]->(v); -DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v12/test_all/snapshot.bin b/tests/integration/durability/tests/v12/test_all/snapshot.bin deleted file mode 100644 index b687336fccdbe5ff1f79453f97ad8aa7499fa6cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 946 zcmb_ZyH3ME5Io)u1uPe?O(2w(Y?BbVg@THYI6E$4TS){dC=g#3iQhotJ1A0tf_LVS zl7fP(MmMv&vpWy>Pt#-5zFWCdBrAZI-4@9b;2mJH*HS%Eu@R+=C}o^{>63j`xeiqB z)VT8<{k8zLBW!wt-+yo24X!zZ0QNc6b!gv$tbOHz;N;gT2fEYzaW)^gvyatlwOo9@ zg{!bfHLJp5*eq<#%hKARe$jq6sBu_wTPV19fA+x91oW^ukcO#Y6#=Mzu*WiXqn~9v3%0E;2bn%v>Mf8)AP!#sB~S diff --git a/tests/integration/durability/tests/v12/test_all/wal.bin b/tests/integration/durability/tests/v12/test_all/wal.bin deleted file mode 100644 index 9d0e369003d6d69f81e3107c67bdfaac289e49d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1023 zcmbVLJx{_=6fKHQ4Dx*VMg~o{CT#&tR~IJ}LO>vd;k9(2ZG;#So&04c{t$nM0~5C8 zemt}>#!k2Qo^#K+_qO+U&soFTKewU{k$8aMQeHu%z2L_0^%jnf0?+AcZ{dVG_MJ}T z>d=j@wHpNju$E2HRGvUG(_x%(VJ1(_f={^h(`xPif~y0qz%gu2{8C3A11f&m*rEn6 zeX_;50p-3KV#tsornn-JWR+B+4HHTnfr&~aE~y+|wNQp7Qk`%Mb*3(GCYrmTcGdaE z_BDCie7>{H#1?3zPpjbEfO6CdPK7K3adDr9LHXY0k0xJDe7TJTKAafJ`Nh$hmuyEo)CenI%EEs3EGFp zkSnYjN@9(s@brWcgB@@<+xf|y#GWadc*zE4M3E*I$TZs6=J(?gLGJc(Z!@3*G4Gz_ wypHWYg@y^L`lf6}g2$z;TzV)gnOUu5p^cWxifr1W!F1t{&3p7onWb4M2P|{EeDuW@32oXyW_2)aS_m%c0?P_}S z<=k^-?&R^|r@rX@|L)BIR%Ql^8}AlYQI+KJD2jums)j*a({)jy+io7Sbi=fbu6h7y|ltY$(>8COY+IP1k~D(H~U>Kzsg^0y)#*T zFRb;=9+W2JnrkW5bUl@9L}nsmtt}UQa-s5a+cb3Un*&OLdAOF6dRq#7TaX(mkjPj| zNwE(uxVPXNDX!g=pr*5r-6n=5u$EG-&Avi5CyEA2u0Ux*=Cv*%f zg|(Cdx8+dC%^ZkiET-hZt!6`lN4t=NHypKHj#J3-tXtLY5!#sYoU3=xH%NBgh%4r% oJaao0ar(C%_8W?rTF=M5JLjdLhzGt~kx1N#^*3tuHIg3Zf6JJj-2eap diff --git a/tests/integration/durability/tests/v12/test_edges/expected.cypher b/tests/integration/durability/tests/v12/test_edges/expected.cypher deleted file mode 100644 index 02dc95c65..000000000 --- a/tests/integration/durability/tests/v12/test_edges/expected.cypher +++ /dev/null @@ -1,45 +0,0 @@ -CREATE INDEX ON :__mg_vertex__(__mg_id__); -CREATE (:__mg_vertex__ {__mg_id__: 0}); -CREATE (:__mg_vertex__ {__mg_id__: 1}); -CREATE (:__mg_vertex__ {__mg_id__: 2}); -CREATE (:__mg_vertex__ {__mg_id__: 3}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 4}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 5}); -CREATE (:__mg_vertex__:`lab` {__mg_id__: 6}); -CREATE (:__mg_vertex__:`lab` {__mg_id__: 7}); -CREATE (:__mg_vertex__:`lab` {__mg_id__: 8}); -CREATE (:__mg_vertex__:`lab2` {__mg_id__: 9}); -CREATE (:__mg_vertex__:`lab2` {__mg_id__: 10}); -CREATE (:__mg_vertex__:`lab2` {__mg_id__: 11}); -CREATE (:__mg_vertex__ {__mg_id__: 12}); -CREATE (:__mg_vertex__ {__mg_id__: 13}); -CREATE (:__mg_vertex__ {__mg_id__: 14}); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`link2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 4 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 4 CREATE (u)-[:`link2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`link2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 4 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 5 CREATE (u)-[:`link`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 4 CREATE (u)-[:`link2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 5 CREATE (u)-[:`link2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 13 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 11 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 12 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 11 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 12 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 13 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 9 CREATE (u)-[:`link3`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 12 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 13 CREATE (u)-[:`link88`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 12 CREATE (u)-[:`link3`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 14 CREATE (u)-[:`selfedge`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 11 CREATE (u)-[:`selfedge2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`link3`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 12 CREATE (u)-[:`selfedge2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 13 CREATE (u)-[:`selfedge2`]->(v); -DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v12/test_edges/snapshot.bin b/tests/integration/durability/tests/v12/test_edges/snapshot.bin deleted file mode 100644 index 3b86dd90fe6ade189c4a8f9b9f00f589750afa79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3123 zcmb8xOK(#_39NcYLFTN%2Yp|-e4RlN!#u`()dK2K zBwp2sSF9F;o!L_3s0z^|gG(_#0UU<8hlqku4`KA^Apq7>PYw?cd>fWB)F;y4;Lb^XX(X gU5rmhvv}>DvX%^U-f-Q$p8QV diff --git a/tests/integration/durability/tests/v12/test_edges/wal.bin b/tests/integration/durability/tests/v12/test_edges/wal.bin deleted file mode 100644 index af4a3b1209565a42b54fb7b05a5d57da136252e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5226 zcmai%>uVE16vgZNt#w_E4@9II5eeEP(i9O12=~M~|OPg#;G5SG8{qw!8d(PR- z&UE@|xO0E!o|&DQ+q_@jMl0j(!FW7mAla8=yQId3Kqq;-Qsuo<5&5&DDpG)=-#9#y4jH`v`$zt z$5>cTnO>Q#8Xe9lf2*4vV_{fO%+Xe?FlevLK;gEs6+XejN2`%yE<-F<7_?VrpfFUV zY=uv;Fx)HVxF4~?puI8!h1<$j7()ZE=ba4LmPg;j_ z$~SB0xF^t5%<)sjtbwj&LZ-5Y3@@#KH9)Fu)^LT^vyLFQtI7 zc4aemiF<}Kp_t<-ix~r5%WN3Kyq8kISi7Fg}G3^A7>7IPNJY&t`NmlSN& zoS}^Dn6nO@;SMS0cyD9QK-V&x&XC|G6>!!rZO(4c8G5KK=F^2bu?&VPLB>BPk1ihNRUeS?FUT)eqbQFB3NrpBc{JSmi2vVo(w5N4Hm=x^Rd(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`edge` {`prop`: 1}]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`edge` {`prop`: false}]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`edge2`]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 9 CREATE (u)-[:`edge2` {`prop`: -3.141}]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`edgelink` {`prop`: 1, `prop2`: {`prop4`: 9}}]->(v); -MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`edgelink` {`prop`: [1, Null, false, ""]}]->(v); -DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v12/test_edges_with_properties/snapshot.bin b/tests/integration/durability/tests/v12/test_edges_with_properties/snapshot.bin deleted file mode 100644 index bc434f4e6aa8503d0fd93994399e1b97977fcb7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1606 zcmb7EJ5Iwu6f}RrpNC=FT?nO%7%L<#6jXFvz{WO0pb&wmIS4iPpx`E)1BqDKnMHcR zf)v(hc4yw~dz-zycp7a))8Cn#dnD!D6iKnYr&d9XoQJz zj}61uMp}>IQBPpQ(6td6I;za8L`>QD!f54Z2)xS(Qu7_BqF7a zw4POGBwG7VA`WXEDrB@mswENitO`AkZw0RzsTqK0WV1JQB2q1hsApA`sH^KAlR6By z6~Y+puF9M0{&sZ7vGI9oR#JMzP&PE50kX|o#(ya-8a6$OyZ&r>o#lg*VV%_3+|*1+k7|r{Y})duhg9{PUACQ6}2hG7g?Ru#{HT5x=ymPtP(TJ((&xl z6r=Nz^4w^sR(yH$FNRa$S@Pm_FRjXliYN_5G99Wlc3JgRh~ZQ)OPh8VsA(vYyenC} zzAQ9hp}IgD41_5{45xxwh1TsrP}5K(c~`OwG;mu-1DB-7B%54~V5o#=IeQG3<^`k* zayp8}wdu3X7TXAhN_bXbv_EYir=w_c(LlVM_~7>Yx%?Qt-Cupx$1!7N{0 fcQ826RAjlxT7wNuMu$fi diff --git a/tests/integration/durability/tests/v12/test_indices/expected.cypher b/tests/integration/durability/tests/v12/test_indices/expected.cypher deleted file mode 100644 index 752f50bc4..000000000 --- a/tests/integration/durability/tests/v12/test_indices/expected.cypher +++ /dev/null @@ -1,16 +0,0 @@ -CREATE INDEX ON :`label2`; -CREATE INDEX ON :`label1`; -CREATE INDEX ON :`label3`; -CREATE INDEX ON :`label`(`prop`); -CREATE INDEX ON :`label2`(`prop`); -CREATE INDEX ON :__mg_vertex__(__mg_id__); -CREATE (:__mg_vertex__:`label` {__mg_id__: 0}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 1}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 2}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: 1}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 4, `prop`: 2}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop`: 3}); -CREATE (:__mg_vertex__:`label2` {__mg_id__: 6, `prop2`: 1}); -CREATE (:__mg_vertex__:`label3` {__mg_id__: 7}); -DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v12/test_indices/snapshot.bin b/tests/integration/durability/tests/v12/test_indices/snapshot.bin deleted file mode 100644 index cf751fc37e9261394ff60a4ff70551eb749dd553..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 898 zcmb7BI}U7r)0P7T5dYOyDCDgm5pk zxUz!wg0LJ*+FU8qtSEilv02|Bl+6*U+qDW1u{j>V5#wKZ{7akdKPpSxRa+FYi1PRA gt+!|23*%ktWswZMG!Eq{4`f)#nC5&{tUguf1tvTqYybcN diff --git a/tests/integration/durability/tests/v12/test_indices/wal.bin b/tests/integration/durability/tests/v12/test_indices/wal.bin deleted file mode 100644 index ba46ff02dcc6efca20876b53eaac0685b2ac48cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2425 zcmah}O-sW-6s+HfiggL(=t0RX&@`>BS1+D~kSXmrZi>u&Tuc~y`xw%dH@vs`Cac?*+<6$|S#e=Gvr2V9t z^pcLy+*U-Zr{GY7^Vdl^?>cu9=T6Ey&%uGI7VPVPi{x|Omo-!y%nfT` zNY=7gYxXwy=4^-b@!6(I0m)2&%vesCD+Q_z=0*xIBx?a$UzyeA2x*Ud8+;gB-O!NE ziK!~Z37rMnGnUg3xl*9oU_wTZ2^f;KEGbaLy$yan1s@xvRSMkwOyKchEGNvB0@Vf+ zGI%#Zkt}A3fhulp@R4zK7+rIwpsjM8(`jIa8O!P3xN@M{V8YQlhkDCFkt}A((QK}~ zwY9ewdwbi!Ws zHb|l8Jet-7JDuKp-?``9+t;g$sJH1%pEEg6kOd#h(~x`v`~bYKOjBBtQ_eg<%L1AZ z%yC}J;@`(e1fWVeTC^ilf3lWMvJdXMmP<5YZNkNR5=TBSTKv&0}QB zMKT%=@+MM1ODfH3M@x286@*+!?hhXNx4ke@F3#VZj_!ncJ&WiG7F)y!bg43}*!Cqy zZKs^g2ts)*$eWo6Ckdcs13Ks(IpMk}2tZZv$+!Sz-J j0}@&iUqS>?5(#nRe{kk! zaO*$mxxXPDJNss0r%`-bKfjsxW_EV{>&Mezw={dct0TWsSAKC4och(*D0E@i{?d#h z=bCZU?KWMQ^qRd^yA{Xn#PwRS^4!w7TJy`!f3WLw^?^TwD45~kV8hb}?k*V!gA(9N zN3DB~V1}_`0z_G1eDvYN(c@58$T>J9vc)dpS1`jh6h#nag^?GTN){3+vj|v%83ioZ z^t7xMp-`+)z|BhGCJL3rc32E$3TC+UVnv9ui;Pq&)K&ZE?e%w~{7tfv*dB|)ykJHF z%UAYUC{`#iJ5y6H??3-e=~>B*bY+f2miE~ac1bY9`<=IRxtc=zfy~RYL_#4S754GT zuLLs+SaBZ^WfvK~GjbSKa3dXHrpJ?!h|i^7HcrS)WmLHgOtD~wCwrlt2wO&>c7kFh zOU_Rn_eOwbr5(6^NXDAw?p?ppPb3!^uCd{5?mIA}fED`=h7|^=nW`I&+_SD>N;RK7 zx={2%uG3nKgTAnJkR!jE{o)J?X81H0{UU0Pfzy^<{FVmAnsw5T;*WZ8$UVT1N-(Q{aeU@8!0~QuA77Mj&Lrac EA3$N0s{jB1 diff --git a/tests/integration/durability/tests/v13/test_all/snapshot.bin b/tests/integration/durability/tests/v13/test_all/snapshot.bin deleted file mode 100644 index 9a0f2c8403dc292b6b2d1bd666980e605897b628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1467 zcmcJOJx;?w5QWWegOFfx*w%qiTCx@^F)b8S^t5rXC2T8+LJBI*!7aD|5(lB;5EN9P z;GJ)gY$6Kc>S^E1o7wl)SC@C=U9bMm)rCdsX{|=;deeeO0dE3kTQ${Hw#!rN7O4#j zwEDE4vTdI3NR{6A^r0Q(`2tD}th!20S0F8GtUy|>|L@X)^BQLWjj6hW>AsNVu+Xff z_kILmHDO{Y0xvY2(thOx=L#V)2lY?+Alx(m6yaS^F z>>#E%FPTgYT>vQd1whG~c&3C3T5LiZAptHSl(ciD*~f{D<7xo{%lenj719ZC&EMzt zTs7u4g_q!FHV1J7+gkV yQO-}otIh7`$9~T`k_=3en^79}&rXvtHaBsYrcpmkO+Lu8EJ{XErgm>7WM$pIHk4 diff --git a/tests/integration/durability/tests/v13/test_all/wal.bin b/tests/integration/durability/tests/v13/test_all/wal.bin deleted file mode 100644 index 84be2a4096a8e44ec6ba381c1e1a1aaf1692987a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1731 zcmb_cO-sW-5Ut;M5Ohu5qX(t8K#8S|S1+D~5~8h$NK;cV4Yo)@PyRB&AL8%u;7QYT z-{-*Yxh&$8+*BdViHR?TUhtCdZasz#OH_L4#!>iYG22p@_iycogxU zuLyz`8ma7*Wj@i(ykj*@YlFxI%uO90AfZ4C&S+RVSWvJh=Rpa$o0hBQkO{2D~Ffg&QJ0)Ai0t0WutAc3m7(jUX43@IdH?_b diff --git a/tests/integration/durability/tests/v13/test_constraints/wal.bin b/tests/integration/durability/tests/v13/test_constraints/wal.bin deleted file mode 100644 index 0c2351e4ae4d2e079140d7d1930acc98873e3a99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmeZuFVEp+fB+{Uc_>RL49ZXzQGv274APP3%M5QWV@Hh;;+@#UYrb`oeT#g1bX(Lq5)M@fT;K?o~EByU5_dr`^+#@gFVm%ZuZ}p|+G_vH=lue~ zZN<*vVbPKP6Zoh|O_I1=Pqr$K*uPjRmK#M|;jN>;H{EWlS*9!6r~2P0(^ZEQxNz1j z_tFtBcwKidUHpRAP4}vM-FE1XLw6mz=g@tJ z9ys*Sp+^p7hf0UK4)q*bb7zkHpS~WEw1-Dv1s6 z^HY3i1y{zcyE0Ma7&U@|gY4-o=b((7gIrnI+{$OAh&S;FLMtuq%BW^nrtq9Jhazd& z;$WB=&Ov)Phl|z#g)7HAhLr``leBAPbh$FR)Rl=MCnmHYq(%13Lg%3EorCO2gQieE zIhxAp31fj)#U})@fP?Ad%EXoZ5jiD$;+=zWh7a^)&y~?ZQlEJ!gH_U^DTt@b6`y!& zTv@I*O0G;4IngU_kl*x;Op+?-NspfVpMJH}Z+!rZ9ufof#Bt zemFfioLhQroyX?;-NEqPc+em3>}>Z(!_85D@^rG@-yTlJTa&?HYcL!o+UKzjSbhB$ D@^xKp diff --git a/tests/integration/durability/tests/v13/test_edges/wal.bin b/tests/integration/durability/tests/v13/test_edges/wal.bin deleted file mode 100644 index 69bc837b59acb8c3892460932d67f5238582c6ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3024 zcmai$%~BIV5XU15nuth-B`ctO1k5c}?2^Rd%7qhEDT-k^aA74{7Ad8aDj&wP@8Q8W z@j-MF=HK06$LX9Vo&NR5{JW=ea{PV#qIUl|GF$#+&i!FG+4HCU57$}#evowshll-c zFW>KV%XekJ+t16vL78O-S>7|=x7IO_{Banx;aB#vtSNGE!8L+pk}4Q}eu%(G@gL1)&RR!>??!2B~KY0!;xKDXPe{pyprp zag{u!N*1V4w&BEnmU>T6=0t$p0H=t|xHNT2^NiB44JY=q2CY3v1Dqlrx+ z{VxV@T$=mWfQzu^m5zn>ZB55Q`?juQp?%xXvCzJ~*0IpOz0tAIzHRDQXy5QxusV~Z sTWH_5^=zSi+tIPmzO{8Mv~RmQ7TUKx9SiN-TOAASTSv#B1TW6TKZ_^e82|tP diff --git a/tests/integration/durability/tests/v13/test_indices/snapshot.bin b/tests/integration/durability/tests/v13/test_indices/snapshot.bin deleted file mode 100644 index 85fd00b1981e52b72069e5576f99a219dbfb3f8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmeZuFV5p-fB+{UXDAB`V=7E)1&qPR2w|xRK^Y7P1~W_%lx7fO!VrP0foq3}s0zWg z!5B(HEHDFLszg|!A_YbH1x9cSL|_U*!f+Rfut62)BqpWi7{M)o8v+(r7EysJvotYF rGfgrv(KWM7G1oOoH8a(Lfg27t4bA`n-d-0# diff --git a/tests/integration/durability/tests/v13/test_indices/wal.bin b/tests/integration/durability/tests/v13/test_indices/wal.bin deleted file mode 100644 index 096167b5111230e9585c64c4e3f7e98f245bb26f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 357 zcmeZuFVEp+fB+{Uc_>RL49ZXzQGv2d3{#U+QWMQ}&5V;$bxo4eOm!_%5{-3Jl2Q^= zlhZ7c%}tDiplTV^;H*dyR;XZ3Vp3`jvIHBlgb_j}3YV5>R0$Clr~w5<`2`5=(ITh@ W6RQm7PLOFxu0gjHhq4%awgLbGY%N>> diff --git a/tests/integration/durability/tests/v13/test_vertices/snapshot.bin b/tests/integration/durability/tests/v13/test_vertices/snapshot.bin deleted file mode 100644 index e1b398cf27da301ff3c0b1ad6f10291bf7139ddc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1584 zcmb7Fy-or_7{osm)I`>WEo?T3vAP5}xNx1(LK{0v6La7-NRV(ecD{$Tk74J7m{?d@ z`2fhl%`BHa6TE8p_S^YpX7`Tk%V@ZtTRi9DB0&axY%WUT2Oz(dRK60RMY1cm3eaK= zLkL#buf_V`+eieUN*P+T*!UO4)V8BVl0we7uys*|X|fz@k)*f8#3m3>usXc?e0_P> zg|jS8dCL}6QXH%+@%(o6J)J!iph}rex50|7f^*2q9!j@>7CRuKEjE!FOUgusHDyj7 zktvDAXfn=9q=1oBYHPpM_%mj;RVg+M$An1kzmoG-T-k?r;jkzd@fy`NW%hM!HegoJZlQj7uO1FD_t5fanv8*LA-uu4m k`$5ME>Y?Yf+U>T}se6Ir_uP7~)#!TNuqBSH?C~G*3q2ZF2><{9 diff --git a/tests/integration/durability/tests/v13/test_vertices/wal.bin b/tests/integration/durability/tests/v13/test_vertices/wal.bin deleted file mode 100644 index 7c6f1e1a5e6e2db57e486aca2175e7656e7392d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmah}%T5A85X86mh%zorJV|_>NRS1>-i#N$n2^cDcrY=G?nQ|jATi$j56^yvH~+-5 z-=MwAR5!aw*wbuHbysyy&s?0nbk}pk?^Nx$mAZ6`J#XKw?t;J%g0NKyt5LI3Z!{W} zR<#*cf?L0OTR-kJJ5gOZZthUcxMlkv?CMa>@ivHp84mUq9IYF6qOLF~0e0GI)^P+g zjAaub$_iuNg+KS6o^^qogF_-q>=J$jGh9Pf1W{HPX^~&aLLzw<0ZTBWfO(6Kmb9WK z6e|>PlTx^eLM5?P7DJhW87@6r5u)rkBi5?vE&KiP{#DMONH!9~T(uR<3uYKgSJo7Y z6$;D_)zsVP`OhzXQ@Oq_jF8CE2G@^M63p;DteJs0`U`7GU zo&%!nIKy>DF2fYAuRY9kf6x>0k<^pM37M&kEiMC7ESTZR9&0DUCQ+!JpjgS0@>9aQ z;h|Y^2W}ptzGk_H@VNiH&6WWy`mcVI>V%k~`%D-2K*)v(tKUv&{vs`>8GgQ7Q6 zoz`L;^s%FZ9QoDk7k5xF!?!u>7f~Y&+`#1dpjiaPN|JPc@#kR?+tfu2SgJ(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 1 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 3 CREATE (u)-[:`link` {`ext`: [false, {`k`: "l"}], `prop`: -1}]->(v); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v13/test_all/expected.cypher b/tests/integration/durability/tests/v14/test_all/expected_wal.cypher similarity index 100% rename from tests/integration/durability/tests/v13/test_all/expected.cypher rename to tests/integration/durability/tests/v14/test_all/expected_wal.cypher diff --git a/tests/integration/durability/tests/v14/test_all/snapshot.bin b/tests/integration/durability/tests/v14/test_all/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..e4452d6df74219f4fbb9e23c1cf83ad15991ef6f GIT binary patch literal 1531 zcmcIjyH3ME5HwFwAvhdPbPfbjos`%Y+i7WN;3F>fS;DcB2vQ&+(eWYt1qD(*gg>CC zpaO;L&LKHO6vWl&c6WAe=6rQ=JKXi=-?_LXB%asiNSI9mPYm7|+;36okw8<>xF^;L zi46i$e9|Xq-jfaPlKY-Kq>Y}>U?2gjrr^^RP_v8`Q1kWwojPz{!x@0uR@s2*J{Je3 zl#nI&egr^qdgGhXSj&UgkB9PQ@;-H0f}4iStneNnZHFHQmjc&~9Jsci#Omdr zHDEOWG_WboOKe6DrT|caUI3I>COi{d3KrM|Y7jAS34uvESL$;(;dQuL0D@)nOZRfo z5#XA??`^qmm|G{RpD^|5f_dbIwE!RjV4`EOO#fgc5DlLdLO{E9y4*N`e6y4$Zk$}Xa6*dGpcEBkIrauM3ACyr=&2X}77{;% z-@$L#sT z6i>QYlodrgnzpkz&RWUQw38D!IqY}y{IH1Pevw3RZ<<7tmg+?PX_Cc7e$?x!jxfZH zkXG-52PU}4CTbCiJLd0c@X+396Yk>j{GuI-yLGtxW@Cj5#Xa-yBjuNM0>stTG89Nj zD>bPIC6~nm;MXP4z-jQr6id_3pVF`Da;dJ;G ztz+Zbj64Yi+8od%acoGjI%|<_A*L!;dWbQCjr$hKQpKNj&3$RRg1GPbMlF{Ib}>S zN-1xo_-lS%D4Nr+sBA)#+-q2G0Um|946xL%Jd|qDdn6kBq`(!8ZB7|eN0d?(4R#B9 zYti7+qW4KOx+Vp#Xl!%Jm?oQ2s-nSeL2oS@Tv~LKM1zeKxT3MmDPx{-cMf?xz-~do z^WWnEE-m`Ni2mTeslKo4bCYt+GmT5)e3?T5D($K&ekbkKn8YnmtH76t@o}~ccYP9oqcGsuF z^|DC%x2I!NKg1cO4eJdXE0M-Et5SLE zDkrs5LGDzcWUX^0z0T`Kd2h3_V7LrgS>vtDtI)xXH_|#&%FOs}7JN}F>j*P>7>gHI COeJ;z literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v14/test_constraints/wal.bin b/tests/integration/durability/tests/v14/test_constraints/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..563560d7e0b6c5158bf8626bd7d7e87cf2182e76 GIT binary patch literal 394 zcmaiuyAHxI3`NUxWU#U`fU-q$18v%sg^5q7syI%kj!3Zc<5a>4j|ETn#z!Zr}mN>hfJXf#s-Y$h&7tlR%qB(?SRZwx;n!yS|rb8swPhGz}`^s&Xk@H zW#qa=3LxS_=hUgSJdU2?Xw_0`ykD(`nY2!=J~6d;QyS({`X5(bsHE{Zk%r{xzuo!I R%z6#!s=w^yO_%;(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ = 3 CREATE (u)-[:`edge` {`prop`: 11}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 5 CREATE (u)-[:`edge` {`prop`: true}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`edge2`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 8 AND v.__mg_id__ = 9 CREATE (u)-[:`edge2` {`prop`: -3.141}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 11 CREATE (u)-[:`edgelink` {`prop`: {`prop`: 1, `prop2`: {`prop4`: 9}}}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 13 CREATE (u)-[:`edgelink` {`prop`: [1, Null, false, "\n\n\n\n\\\"\"\n\t"]}]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 0 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 1 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 2 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 3 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 4 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 5 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 6 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 7 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 8 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 9 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 10 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 11 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 12 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 13 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 14 CREATE (u)-[:`testedge`]->(v); +MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 15 AND v.__mg_id__ = 15 CREATE (u)-[:`testedge`]->(v); +DROP INDEX ON :__mg_vertex__(__mg_id__); +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; diff --git a/tests/integration/durability/tests/v14/test_edges/snapshot.bin b/tests/integration/durability/tests/v14/test_edges/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..2fecc348998c2fcf9c3bd1f0d41a33cfb0d10971 GIT binary patch literal 4188 zcmb7{%Tg3U5Jktg;0ua@_KDTQYt<19(+$klFv5-QT)G*c$1o(20RG0c|KY;VaA$-c z;14JxeNT;E#=zN3#?8vio0XYW)vsP0AKY!7{ho!FjTC-dIg`TTy+-ItcrM|qgzt@z zhsL~xkcUfhiEy<+vT&=B@^E?bs@b1;XxNPVm6sGiNH+KS+oz9T;?(CnT$}87K{4Ah zJUsevnBlK2+Y!8xEr|agc$tMMlDIxO*t}vS|Jh77+sfK9pFaJ)>2#*2@^C|bn*UZF zZaO5xvAv%5RoddQRkwZB9f#BuV}?zc$Ez^ohO-XIhyG6!n)6j{hvpqxa42)=zC#Zj zdg#z2haNlh#Gyrpo;nmA${p%BwB*pTLn{ug{tZQ{aE_pvNIgg&&~I!v{eVg{l01WG zgd+o%7Bv)GKIdtT?9(VXGpg>)6e6ahMo@}D_9WynXd{n7t_YBp5HFM>_7IYNV8opn z-R#ViY79DuQqt0;jp5Aj7>tL>UKN6s@2Y0nM2bZNc2X~w|2bYXD2X})vN7S~!86lU-o1-HY zEshmukv9j2fj0-E`#*(SR*$A{o)w2W>0O}$@sYSc*s1r?x3xX>-rIWr-NAbvL>P=T z8@!nKHXXs-ptw1yk57JURASbTK3dnHy42Rr^vk_!Z+Ej>46A-wlv{PL*zON^ia|Z> zcdL51KIqph-mQ9NzpAQYs~YYW<$75c{mq^2qT1>8HU?YWs_YHIkt($&LC3)_Gx=(6 literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v14/test_edges/wal.bin b/tests/integration/durability/tests/v14/test_edges/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..70023e0406c24d5804d047a328d3381f3ff985b5 GIT binary patch literal 6429 zcmai&TW=dx6otc`gj+IoGu#Pjun@v1iD!}+A%rRf2my+w1CAiV*zvd&Oj}9>yz?`7 z=6~?OKjDYKjyr2Dr*r0PKaIBc`qrLv+2`1wfBf70*_HdB>&a{NQ}RdsaILdbKMk^J zK0Q0w>&@~(*2@m7e(z{7oAk!jY_OMCv)%EaV)9wu&jxv(_YU*fSufkos@~vWa@5Nw z{r>*=a4*mL?!boum6=b1V<8vS&0^lNqRy!w4K>Z~qDZ%s#|Zt`#)I;L2$g0V19 znWZut9x1<5{z*4^l*SL$Pl^=@Vr|Aym&yzngCepSKTc!3yJ7{$qs!8b;%NDyl?MrNtZfH5d?Gh=MB zV=gnr3dY)up)QpfFvb!T*^E1UV<;3W62#h!p)QpP$)a^#G^v!|iEPH3G{y>5tl*k& zGlsfUX27^fHsj}M{1oRHu_8gN%@~=bG6Tk-$jyvjq%pRJVg)x_n=#a-G6TjbqVvKhZhW9&)A3hweYW2l!h(JkiVNi`R%6f00#)7whhVrkFS zPl^=@Vr`{Cy_EStX~-zW3X}#Pd0T1QbwOWZTd#D`o~!B2icy}Ol9+mjY8;=J)i~RD z4oNa3k&?Uj?%Yke)Rk&RT^a#N)uK$)%=RzIP^T}l7DgNYDaRb%qyx^y4o3OHVW0h- z{Z<;2zePDr9+0CUE;T4#9Mznm{t6gDz3B{RZDBw)=(2$*om zjPMBwSNs|<;gT8QQxb4ni`0M#m&^#Ck#N_am~hFAa7;qlS_6a_ zYx_wJnGp&SaEEKwK!W-MWJV}Sc)_nh{Q)u~d_@ATbCDX+k+AI()E^)-!uKRx^9kw?kQw0z5_Wuo`U7M}n3M2^Pf&k=%m}wh z==ua~Qpt>Pr`DZ@uk5^i@Mmc^`MH+dV7~}!l^Ok?Y+ZRbc=z#%=rqj=(@RA36Q9UT zELy(vWg_~EPh_S;M8ENg%yfw8KR%I}4iWvxCo&A^e1k_>GXruS>4&Kn1GSoy_y}b9m8K5~m zuB&V~M|O_tk9bn_BC(zfify9ESri$IOvYwJCnqseihdnx?Z-jX-?BONh~nh*_gl|s zEMG*fa~`{NwPb(bGR^19Z@N6BV8`>Ah&B|Jass^e3MxbmMhk>e)F_=++7uzd=#6$5 dB(x#aP>I5Vm&yWqVXeFv)GCZGi5td$djo7yBG>=` literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v14/test_indices/wal.bin b/tests/integration/durability/tests/v14/test_indices/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d9f533e5a618c4ebd9db6c234e385840bbdce92 GIT binary patch literal 274 zcmZ{cyAHxI3`NUxWQejefU;Rinxsxv7A8IbF(t*BDp9fU0sJ_qxD`Ue)3NXIJslt2 zruxi5Y?;K9VJG)YrY+j}WFTYq;D`_c8iLw1WD|M}x=b?VfQgEQwv+~((Gjxs5h$A& z?KKekBDlCx6P@Lnk6p^t)k@5Gzx*U}#X*0+^>1%Jd1Ej%$mN%&#Nv-N{tRU=_8BL0 literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v12/test_vertices/expected.cypher b/tests/integration/durability/tests/v14/test_vertices/expected_snapshot.cypher similarity index 78% rename from tests/integration/durability/tests/v12/test_vertices/expected.cypher rename to tests/integration/durability/tests/v14/test_vertices/expected_snapshot.cypher index 9ae1e6f32..ecdc1229e 100644 --- a/tests/integration/durability/tests/v12/test_vertices/expected.cypher +++ b/tests/integration/durability/tests/v14/test_vertices/expected_snapshot.cypher @@ -5,9 +5,9 @@ CREATE (:__mg_vertex__:`label` {__mg_id__: 2, `prop`: false}); CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: true}); CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop`: 1}); CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop2`: 3.141}); -CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop2`: -314000000, `prop3`: true}); -CREATE (:__mg_vertex__:`label1`:`label2`:`label3` {__mg_id__: 7}); -CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop`: 1, `prop2`: 2, `prop3`: "str"}); +CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop3`: true, `prop2`: -314000000}); +CREATE (:__mg_vertex__:`label2`:`label3`:`label1` {__mg_id__: 7}); +CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop3`: "str", `prop2`: 2, `prop`: 1}); CREATE (:__mg_vertex__:`label1`:`label2` {__mg_id__: 9, `prop`: {`prop_nes`: "kaj je"}}); CREATE (:__mg_vertex__:`label` {__mg_id__: 10, `prop_array`: [1, false, Null, "str", {`prop2`: 2}]}); CREATE (:__mg_vertex__:`label`:`label3` {__mg_id__: 11, `prop`: {`prop`: [1, false], `prop2`: {}, `prop3`: "test2", `prop4`: "test"}}); diff --git a/tests/integration/durability/tests/v13/test_vertices/expected.cypher b/tests/integration/durability/tests/v14/test_vertices/expected_wal.cypher similarity index 94% rename from tests/integration/durability/tests/v13/test_vertices/expected.cypher rename to tests/integration/durability/tests/v14/test_vertices/expected_wal.cypher index 9ae1e6f32..d8f758737 100644 --- a/tests/integration/durability/tests/v13/test_vertices/expected.cypher +++ b/tests/integration/durability/tests/v14/test_vertices/expected_wal.cypher @@ -6,7 +6,7 @@ CREATE (:__mg_vertex__:`label` {__mg_id__: 3, `prop`: true}); CREATE (:__mg_vertex__:`label2` {__mg_id__: 4, `prop`: 1}); CREATE (:__mg_vertex__:`label` {__mg_id__: 5, `prop2`: 3.141}); CREATE (:__mg_vertex__:`label6` {__mg_id__: 6, `prop2`: -314000000, `prop3`: true}); -CREATE (:__mg_vertex__:`label1`:`label2`:`label3` {__mg_id__: 7}); +CREATE (:__mg_vertex__:`label2`:`label3`:`label1` {__mg_id__: 7}); CREATE (:__mg_vertex__:`label` {__mg_id__: 8, `prop`: 1, `prop2`: 2, `prop3`: "str"}); CREATE (:__mg_vertex__:`label1`:`label2` {__mg_id__: 9, `prop`: {`prop_nes`: "kaj je"}}); CREATE (:__mg_vertex__:`label` {__mg_id__: 10, `prop_array`: [1, false, Null, "str", {`prop2`: 2}]}); diff --git a/tests/integration/durability/tests/v14/test_vertices/snapshot.bin b/tests/integration/durability/tests/v14/test_vertices/snapshot.bin new file mode 100644 index 0000000000000000000000000000000000000000..690d0fa3d39926a378367dca195cefe99bdeeafb GIT binary patch literal 1648 zcma)6u}&N@6ublyI1&h$1$W3IAgYzJ>)YGA4$(kCx|EU7nll9;KzG;B@gLOrjCA>v zR1`?m2nlY%GiGHYvCY1H9?zRMe%_zEqqBw9%d;c)UF3tC>6esvK=AF8%iJNjcYzXa zd+R-c5+6{F!AE4?CQ^c+a2qpIjzBI&v?zgPFCioaMlL;u?I_{wXnBe-LkbTH8dJi# z_lhYL1B_h$_5aWHKh@rjY5ENN=mRsPB$K%MyLbCIdBhCOigt=+?;1OuMZq=#_l~+w zH04nhoMH{>E%S;Aha{O`<7hmLIYM3B$_$&J8gR)~q!LKJ7@l7ooY|2~;OI$ptd4E0 z<{Z2LOJridIE-BJ-2pzCS-IHg?3EzEYeBj6%z^JppoRz01hnN%_PxWG1VLR&u3)b} zn2FA$DQ6Wb8hI`GIkuy5&aaxdBUz_jjQRN$eu6!sI>AJ!x%I8~@6C{BBTPk5>w@zE z`x2bz^9)qOVRcCmr{T0R_B0dlwVBtvVU5zo;pdM zHEz*{@5zQUH0zh;k+y?WTYHptvcVv&%B-JiZEQc+MP6n-g1cpPXiSx*+H`H&8T9hh Xbn-Ga#ui1{D-O+%LaYTxHt#Y|vlU|w literal 0 HcmV?d00001 diff --git a/tests/integration/durability/tests/v14/test_vertices/wal.bin b/tests/integration/durability/tests/v14/test_vertices/wal.bin new file mode 100644 index 0000000000000000000000000000000000000000..fa3417b03ec0ab4b4480a0280997190316e23f2b GIT binary patch literal 4172 zcmai0%We}v5HxQR9!iEy7J(2E4|7@x@7meqhPc25DVoe75|LKBc!LN5BFEx!L*g?y z^BLUu6V7}?7&}wlGufGpPwU?5>gt+yk6*p`a&WVE@_8=qnKS?(3> zI4jzAFB=ZVftQqEI)->&CRBnvk zG*3su5cm4@^g6j=f>rh5ZS}dGOxv$jl`PoNcjKz+i}NP5MadkK6!Y^jQZceqBc*qY z-|35sRQQ~E;+PbOg$g6vLS8a72X)gPFqdL3k2KU_yq0#Eq`@7INpdVC4cQj*lr$g| zb9tn7Yg>5qe>nMcm@ez12d9>QmE>O_XAzSEv5mpBYslUH|{#jdi##r43cG?zA7{?^VLTw=1LOQ3h&SZ+oJbfU` zi+B4F-+#aScdS2AY>_ToAl>W|o!J%onVxeX|NXVX_S-}M?G6ynSKG9CXn~O-e22qnM+%-N>*U0a!k^i4q1V03yC8zvjT)-E>D}?+7=!!=l#@^N&PKd zL6wyDvc4eGCFjUfwgm9b3_+fD;x;*PjrIa!QXm#`!h?kC*b5#AbQ!aNP+ZPq7NoFb z3-8VZz9o2=8IZ2vUx(vf0x>5h!I_>D*FP%2rtQ%*r*E^QVM=voFIk^<_YS#>JB?$K z?$MCD$X1N>7}I68@Qwos_U78+gC`5Sk6DYBde=zZ^{esy!M*jD(CZV$sGK2^nF;am5)48DYfS#u*72VZ;wS&Pd1zBgQh$ xNXQ5yE`vBDAtQ`<9cLsY9{6H&<`D}%&Pd1zBUWUbk&qEa?3OqqA(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 0 CREATE (u)-[:`KNOWS` {`value`: ["6", "hmmm"]}]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/array_types_multi_char_array_delimiter/expected.cypher b/tests/integration/mg_import_csv/tests/array_types_multi_char_array_delimiter/expected.cypher index 1010ec3a7..eaeb385cd 100644 --- a/tests/integration/mg_import_csv/tests/array_types_multi_char_array_delimiter/expected.cypher +++ b/tests/integration/mg_import_csv/tests/array_types_multi_char_array_delimiter/expected.cypher @@ -7,4 +7,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment`:`Entry4`:`this`:`is`:`a`:` lot `:`of` MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`KNOWS` {`value`: ["5", " asd", " helloworld"]}]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 0 CREATE (u)-[:`KNOWS` {`value`: ["6", "hmmm"]}]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/bad_relationships/expected.cypher b/tests/integration/mg_import_csv/tests/bad_relationships/expected.cypher index 2a1d636b0..7b027c9d2 100644 --- a/tests/integration/mg_import_csv/tests/bad_relationships/expected.cypher +++ b/tests/integration/mg_import_csv/tests/bad_relationships/expected.cypher @@ -8,4 +8,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 2 CREATE (u)-[:`VISITED`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`FOLLOWS`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test1/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test1/expected.cypher index d1a82054c..c0c6681e2 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test1/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test1/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `id`: "0"}); CREATE (:__mg_vertex__ {__mg_id__: 1, `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test10/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test10/expected.cypher index 8cb1b3423..3b31059ff 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test10/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test10/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "world fgh", `id`: CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "will this work?", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test12/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test12/expected.cypher index 842547e77..aebb44de1 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test12/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test12/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "ÖworldÖÖÖ fÖ CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wilÖl this work?Ö", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test13/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test13/expected.cypher index 62ed88e89..bfcc24e55 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test13/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test13/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "Öworld,Ö,ÖÖ, CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wilÖl t,his work?Ö", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test15/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test15/expected.cypher index 27395b85d..f34ad658b 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test15/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test15/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "woÖrld", `id`: " CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wilÖl this work?ÖÖ", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test16/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test16/expected.cypher index d1a82054c..c0c6681e2 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test16/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test16/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `id`: "0"}); CREATE (:__mg_vertex__ {__mg_id__: 1, `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test17/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test17/expected.cypher index 36e192a04..9895424be 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test17/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test17/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "world", `id`: "0" CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "my", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test18/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test18/expected.cypher index 8cb1b3423..3b31059ff 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test18/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test18/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "world fgh", `id`: CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "will this work?", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test2/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test2/expected.cypher index 36e192a04..9895424be 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test2/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test2/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "world", `id`: "0" CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "my", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test20/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test20/expected.cypher index 661e6ba9c..a8d1b383b 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test20/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test20/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "\"world\"\"\" f\" CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l this work?\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test21/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test21/expected.cypher index b3ac07ad0..2ac5c9e1f 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test21/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test21/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "\"worldÖ\"Ö\"\" CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l tÖhis work?\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test23/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test23/expected.cypher index a704d8646..bee659585 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test23/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test23/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "wo\"rld", `id`: " CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l this work?\"\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test3/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test3/expected.cypher index 8cb1b3423..3b31059ff 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test3/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test3/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "world fgh", `id`: CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "will this work?", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test5/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test5/expected.cypher index 661e6ba9c..a8d1b383b 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test5/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test5/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "\"world\"\"\" f\" CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l this work?\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test6/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test6/expected.cypher index 077c59129..890d66d1a 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test6/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test6/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "\"world,\",\"\", CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l t,his work?\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test8/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test8/expected.cypher index a704d8646..bee659585 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test8/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test8/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `test`: "asd", `value`: "wo\"rld", `id`: " CREATE (:__mg_vertex__ {__mg_id__: 1, `test`: "string", `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `test`: "wil\"l this work?\"\"", `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/csv_parser_test9/expected.cypher b/tests/integration/mg_import_csv/tests/csv_parser_test9/expected.cypher index d1a82054c..c0c6681e2 100644 --- a/tests/integration/mg_import_csv/tests/csv_parser_test9/expected.cypher +++ b/tests/integration/mg_import_csv/tests/csv_parser_test9/expected.cypher @@ -3,4 +3,4 @@ CREATE (:__mg_vertex__ {__mg_id__: 0, `id`: "0"}); CREATE (:__mg_vertex__ {__mg_id__: 1, `id`: "1"}); CREATE (:__mg_vertex__ {__mg_id__: 2, `value`: "hello", `id`: "2"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/data_split_into_multiple_files/expected.cypher b/tests/integration/mg_import_csv/tests/data_split_into_multiple_files/expected.cypher index 9f16cd5ed..3c9a6ed88 100644 --- a/tests/integration/mg_import_csv/tests/data_split_into_multiple_files/expected.cypher +++ b/tests/integration/mg_import_csv/tests/data_split_into_multiple_files/expected.cypher @@ -14,4 +14,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 6 CREATE (u)-[:`NOT_NEIGHBOUR`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 0 CREATE (u)-[:`NOT_NEIGHBOUR`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/duplicate_nodes/expected.cypher b/tests/integration/mg_import_csv/tests/duplicate_nodes/expected.cypher index 1e0497181..6b6cb2fec 100644 --- a/tests/integration/mg_import_csv/tests/duplicate_nodes/expected.cypher +++ b/tests/integration/mg_import_csv/tests/duplicate_nodes/expected.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/extra_columns_nodes/expected.cypher b/tests/integration/mg_import_csv/tests/extra_columns_nodes/expected.cypher index 1e0497181..6b6cb2fec 100644 --- a/tests/integration/mg_import_csv/tests/extra_columns_nodes/expected.cypher +++ b/tests/integration/mg_import_csv/tests/extra_columns_nodes/expected.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/extra_columns_relationships/expected.cypher b/tests/integration/mg_import_csv/tests/extra_columns_relationships/expected.cypher index 2a1d636b0..7b027c9d2 100644 --- a/tests/integration/mg_import_csv/tests/extra_columns_relationships/expected.cypher +++ b/tests/integration/mg_import_csv/tests/extra_columns_relationships/expected.cypher @@ -8,4 +8,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 2 CREATE (u)-[:`VISITED`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`FOLLOWS`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/field_types/expected.cypher b/tests/integration/mg_import_csv/tests/field_types/expected.cypher index e9c3d2b73..9bed35149 100644 --- a/tests/integration/mg_import_csv/tests/field_types/expected.cypher +++ b/tests/integration/mg_import_csv/tests/field_types/expected.cypher @@ -1,4 +1,4 @@ CREATE INDEX ON :__mg_vertex__(__mg_id__); CREATE (:__mg_vertex__ {__mg_id__: 0, `value_bool`: false, `value_boolean`: true, `value_integer`: 5, `value_float`: 2.718, `value_double`: 3.141, `value_short`: 4, `value_byte`: 3, `value_long`: 2, `value_int`: 1, `value_char`: "world", `value_str`: "hello", `id`: "0"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/id_type/expected_integer.cypher b/tests/integration/mg_import_csv/tests/id_type/expected_integer.cypher index cd0528e4b..b0792f640 100644 --- a/tests/integration/mg_import_csv/tests/id_type/expected_integer.cypher +++ b/tests/integration/mg_import_csv/tests/id_type/expected_integer.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: 3}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: 4}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/id_type/expected_string.cypher b/tests/integration/mg_import_csv/tests/id_type/expected_string.cypher index 1e0497181..6b6cb2fec 100644 --- a/tests/integration/mg_import_csv/tests/id_type/expected_string.cypher +++ b/tests/integration/mg_import_csv/tests/id_type/expected_string.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_disabled.cypher b/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_disabled.cypher index 6bbbffa50..637dc86f2 100644 --- a/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_disabled.cypher +++ b/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_disabled.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `bro CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_enabled.cypher b/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_enabled.cypher index 1e0497181..6b6cb2fec 100644 --- a/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_enabled.cypher +++ b/tests/integration/mg_import_csv/tests/ignore_empty_strings/expected_enabled.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/ignored_columns/expected.cypher b/tests/integration/mg_import_csv/tests/ignored_columns/expected.cypher index 87677ec8b..7578cdfc9 100644 --- a/tests/integration/mg_import_csv/tests/ignored_columns/expected.cypher +++ b/tests/integration/mg_import_csv/tests/ignored_columns/expected.cypher @@ -7,4 +7,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `br MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`KNOWS` {`value`: 5}]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 0 CREATE (u)-[:`KNOWS` {`value`: 6}]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/multi_char_quote_and_node_label/expected.cypher b/tests/integration/mg_import_csv/tests/multi_char_quote_and_node_label/expected.cypher index 965cc7873..183427636 100644 --- a/tests/integration/mg_import_csv/tests/multi_char_quote_and_node_label/expected.cypher +++ b/tests/integration/mg_import_csv/tests/multi_char_quote_and_node_label/expected.cypher @@ -15,4 +15,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 3 AND v.__mg_id__ = 8 CREATE (u)-[:`POSTED_ON`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 9 CREATE (u)-[:`POSTED_ON`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/neo_example/expected.cypher b/tests/integration/mg_import_csv/tests/neo_example/expected.cypher index 84f56aaeb..0420ae6c0 100644 --- a/tests/integration/mg_import_csv/tests/neo_example/expected.cypher +++ b/tests/integration/mg_import_csv/tests/neo_example/expected.cypher @@ -15,4 +15,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 1 CREATE (u)-[:`ACTED_IN` {`role`: "Trinity"}]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 2 CREATE (u)-[:`ACTED_IN` {`role`: "Trinity"}]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/node_id/expected.cypher b/tests/integration/mg_import_csv/tests/node_id/expected.cypher index 1ed2ef7ed..5707ee780 100644 --- a/tests/integration/mg_import_csv/tests/node_id/expected.cypher +++ b/tests/integration/mg_import_csv/tests/node_id/expected.cypher @@ -19,4 +19,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 7 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 11 CREATE (u)-[:`NOT_NEIGHBOUR`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 5 CREATE (u)-[:`NOT_NEIGHBOUR`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/relationship_properties/expected.cypher b/tests/integration/mg_import_csv/tests/relationship_properties/expected.cypher index 056b411ee..c6d7bc379 100644 --- a/tests/integration/mg_import_csv/tests/relationship_properties/expected.cypher +++ b/tests/integration/mg_import_csv/tests/relationship_properties/expected.cypher @@ -14,4 +14,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 2 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 4 AND v.__mg_id__ = 6 CREATE (u)-[:`NOT_NEIGHBOUR` {`value`: "world"}]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 0 CREATE (u)-[:`NOT_NEIGHBOUR` {`value`: "to properties"}]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/single_char_quote_and_relationship_type/expected.cypher b/tests/integration/mg_import_csv/tests/single_char_quote_and_relationship_type/expected.cypher index 92897add0..f6b056b49 100644 --- a/tests/integration/mg_import_csv/tests/single_char_quote_and_relationship_type/expected.cypher +++ b/tests/integration/mg_import_csv/tests/single_char_quote_and_relationship_type/expected.cypher @@ -8,4 +8,4 @@ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 2 CREATE (u)-[:`TYPE`]->(v); MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`TYPE`]->(v); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/trim_strings/expected_disabled.cypher b/tests/integration/mg_import_csv/tests/trim_strings/expected_disabled.cypher index 3c0327834..d90d18fb4 100644 --- a/tests/integration/mg_import_csv/tests/trim_strings/expected_disabled.cypher +++ b/tests/integration/mg_import_csv/tests/trim_strings/expected_disabled.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `bro CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: " I see ", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: " fine", `browser`: "Internet Explorer ", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/integration/mg_import_csv/tests/trim_strings/expected_enabled.cypher b/tests/integration/mg_import_csv/tests/trim_strings/expected_enabled.cypher index 1e0497181..6b6cb2fec 100644 --- a/tests/integration/mg_import_csv/tests/trim_strings/expected_enabled.cypher +++ b/tests/integration/mg_import_csv/tests/trim_strings/expected_enabled.cypher @@ -5,4 +5,4 @@ CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 2, `content`: "LOL", `cou CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 3, `content`: "I see", `browser`: "Firefox", `country`: "France", `id`: "3"}); CREATE (:__mg_vertex__:`Message`:`Comment` {__mg_id__: 4, `content`: "fine", `browser`: "Internet Explorer", `country`: "Italy", `id`: "4"}); DROP INDEX ON :__mg_vertex__(__mg_id__); -MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; +MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__; \ No newline at end of file diff --git a/tests/unit/storage_v2_decoder_encoder.cpp b/tests/unit/storage_v2_decoder_encoder.cpp index a20714664..ede4c57e0 100644 --- a/tests/unit/storage_v2_decoder_encoder.cpp +++ b/tests/unit/storage_v2_decoder_encoder.cpp @@ -323,6 +323,7 @@ TEST_F(DecoderEncoderTest, PropertyValueInvalidMarker) { case storage::durability::Marker::SECTION_INDICES: case storage::durability::Marker::SECTION_CONSTRAINTS: case storage::durability::Marker::SECTION_DELTA: + case storage::durability::Marker::SECTION_EPOCH_HISTORY: case storage::durability::Marker::SECTION_OFFSETS: case storage::durability::Marker::DELTA_VERTEX_CREATE: case storage::durability::Marker::DELTA_VERTEX_DELETE: diff --git a/tests/unit/storage_v2_replication.cpp b/tests/unit/storage_v2_replication.cpp index c3a582758..23dae1d67 100644 --- a/tests/unit/storage_v2_replication.cpp +++ b/tests/unit/storage_v2_replication.cpp @@ -7,8 +7,9 @@ #include #include -#include +#include #include +#include "storage/v2/view.hpp" using testing::UnorderedElementsAre; @@ -428,7 +429,6 @@ TEST_F(ReplicationTest, RecoveryProcess) { { storage::Storage replica_store( {.durability = {.storage_directory = replica_storage_directory, - .recover_on_startup = true, .snapshot_wal_mode = storage::Config::Durability:: SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}}); @@ -438,9 +438,10 @@ TEST_F(ReplicationTest, RecoveryProcess) { main_store.RegisterReplica("REPLICA1", io::network::Endpoint{"127.0.0.1", 10000}); - ASSERT_EQ(main_store.ReplicaState("REPLICA1"), + ASSERT_EQ(main_store.GetReplicaState("REPLICA1"), storage::replication::ReplicaState::RECOVERY); - while (main_store.ReplicaState("REPLICA1") != + + while (main_store.GetReplicaState("REPLICA1") != storage::replication::ReplicaState::READY) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -534,15 +535,15 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) { ASSERT_FALSE(acc.Commit().HasError()); if (i == 0) { - ASSERT_EQ(main_store.ReplicaState("REPLICA_ASYNC"), + ASSERT_EQ(main_store.GetReplicaState("REPLICA_ASYNC"), storage::replication::ReplicaState::REPLICATING); } else { - ASSERT_EQ(main_store.ReplicaState("REPLICA_ASYNC"), + ASSERT_EQ(main_store.GetReplicaState("REPLICA_ASYNC"), storage::replication::ReplicaState::RECOVERY); } } - while (main_store.ReplicaState("REPLICA_ASYNC") != + while (main_store.GetReplicaState("REPLICA_ASYNC") != storage::replication::ReplicaState::READY) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } @@ -557,3 +558,107 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) { return exists; })); } + +TEST_F(ReplicationTest, EpochTest) { + storage::Storage main_store( + {.items = {.properties_on_edges = true}, + .durability = { + .storage_directory = storage_directory, + .snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode:: + PERIODIC_SNAPSHOT_WITH_WAL, + }}); + + storage::Storage replica_store1( + {.items = {.properties_on_edges = true}, + .durability = { + .storage_directory = storage_directory, + .snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode:: + PERIODIC_SNAPSHOT_WITH_WAL, + }}); + + replica_store1.SetReplicationRole( + io::network::Endpoint{"127.0.0.1", 10000}); + + storage::Storage replica_store2( + {.items = {.properties_on_edges = true}, + .durability = { + .storage_directory = storage_directory, + .snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode:: + PERIODIC_SNAPSHOT_WITH_WAL, + }}); + + replica_store2.SetReplicationRole( + io::network::Endpoint{"127.0.0.1", 10001}); + + main_store.RegisterReplica("REPLICA1", + io::network::Endpoint{"127.0.0.1", 10000}); + + main_store.RegisterReplica("REPLICA2", + io::network::Endpoint{"127.0.0.1", 10001}); + + std::optional vertex_gid; + { + auto acc = main_store.Access(); + const auto v = acc.CreateVertex(); + vertex_gid.emplace(v.Gid()); + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = replica_store1.Access(); + const auto v = acc.FindVertex(*vertex_gid, storage::View::OLD); + ASSERT_TRUE(v); + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = replica_store2.Access(); + const auto v = acc.FindVertex(*vertex_gid, storage::View::OLD); + ASSERT_TRUE(v); + ASSERT_FALSE(acc.Commit().HasError()); + } + + main_store.UnregisterReplica("REPLICA1"); + main_store.UnregisterReplica("REPLICA2"); + + replica_store1.SetReplicationRole(); + replica_store1.RegisterReplica("REPLICA2", + io::network::Endpoint{"127.0.0.1", 10001}); + + { + auto acc = main_store.Access(); + acc.CreateVertex(); + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = replica_store1.Access(); + auto v = acc.CreateVertex(); + vertex_gid.emplace(v.Gid()); + ASSERT_FALSE(acc.Commit().HasError()); + } + // Replica1 should forward it's vertex to Replica2 + { + auto acc = replica_store2.Access(); + const auto v = acc.FindVertex(*vertex_gid, storage::View::OLD); + ASSERT_TRUE(v); + ASSERT_FALSE(acc.Commit().HasError()); + } + + replica_store1.SetReplicationRole( + io::network::Endpoint{"127.0.0.1", 10000}); + main_store.RegisterReplica("REPLICA1", + io::network::Endpoint{"127.0.0.1", 10000}); + + { + auto acc = main_store.Access(); + const auto v = acc.CreateVertex(); + vertex_gid.emplace(v.Gid()); + ASSERT_FALSE(acc.Commit().HasError()); + } + // Replica1 is not compatible with the main so it shouldn't contain + // it's newest vertex + { + auto acc = replica_store1.Access(); + const auto v = acc.FindVertex(*vertex_gid, storage::View::OLD); + ASSERT_FALSE(v); + ASSERT_FALSE(acc.Commit().HasError()); + } +} diff --git a/tests/unit/storage_v2_wal_file.cpp b/tests/unit/storage_v2_wal_file.cpp index c8065a0f6..c032b3744 100644 --- a/tests/unit/storage_v2_wal_file.cpp +++ b/tests/unit/storage_v2_wal_file.cpp @@ -190,8 +190,9 @@ class DeltaGenerator final { DeltaGenerator(const std::filesystem::path &data_directory, bool properties_on_edges, uint64_t seq_num) : uuid_(utils::GenerateUUID()), + epoch_id_(utils::GenerateUUID()), seq_num_(seq_num), - wal_file_(data_directory, uuid_, + wal_file_(data_directory, uuid_, epoch_id_, {.properties_on_edges = properties_on_edges}, &mapper_, seq_num, &file_retainer_) {} @@ -249,6 +250,7 @@ class DeltaGenerator final { 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_, @@ -267,6 +269,7 @@ class DeltaGenerator final { } std::string uuid_; + std::string epoch_id_; uint64_t seq_num_; uint64_t transaction_id_{storage::kTransactionInitialId}; @@ -303,6 +306,7 @@ class DeltaGenerator final { void AssertWalInfoEqual(const storage::durability::WalInfo &a, const 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);