From 9d056e7649b6f1e59118b46889ee2f96bf6fe0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Budiseli=C4=87?= <marko.budiselic@memgraph.com> Date: Thu, 29 Jun 2023 11:44:55 +0200 Subject: [PATCH] Add experimental/v1 of ON_DISK_TRANSACTIONAL storage (#850) Co-authored-by: Andi Skrgat <andi8647@gmail.com> Co-authored-by: Aidar Samerkhanov <aidar.samerkhanov@memgraph.io> --- include/mg_procedure.h | 2 +- src/http_handlers/metrics.hpp | 2 +- src/memgraph.cpp | 43 +- src/mg_import_csv.cpp | 35 +- src/query/db_accessor.cpp | 2 +- src/query/db_accessor.hpp | 14 + src/query/dump.cpp | 3 +- src/query/exceptions.hpp | 31 + src/query/frontend/ast/ast.hpp | 2 +- .../frontend/ast/cypher_main_visitor.cpp | 5 +- .../opencypher/grammar/MemgraphCypher.g4 | 2 +- .../opencypher/grammar/MemgraphCypherLexer.g4 | 1 + .../interpret/awesome_memgraph_functions.cpp | 6 +- src/query/interpreter.cpp | 297 +- src/query/interpreter.hpp | 22 +- src/query/plan/operator.cpp | 38 +- src/query/plan/operator.hpp | 1 + src/query/plan/rewrite/index_lookup.hpp | 1 - src/query/procedure/mg_procedure_impl.cpp | 88 +- src/query/procedure/mg_procedure_impl.hpp | 45 +- src/query/stream/streams.cpp | 4 +- src/query/trigger_context.cpp | 4 +- src/query/trigger_context.hpp | 7 +- src/query/typed_value.hpp | 2 +- src/storage/README.md | 3 + src/storage/v2/CMakeLists.txt | 15 +- src/storage/v2/all_vertices_iterable.cpp | 44 + src/storage/v2/all_vertices_iterable.hpp | 58 + src/storage/v2/config.hpp | 11 + src/storage/v2/constraints.hpp | 199 - .../v2/constraints/constraint_violation.hpp | 39 + src/storage/v2/constraints/constraints.hpp | 48 + .../v2/constraints/existence_constraints.cpp | 58 + .../v2/constraints/existence_constraints.hpp | 62 + .../v2/constraints/unique_constraints.hpp | 75 + src/storage/v2/delta.hpp | 35 +- src/storage/v2/disk/label_index.cpp | 215 + src/storage/v2/disk/label_index.hpp | 62 + src/storage/v2/disk/label_property_index.cpp | 228 + src/storage/v2/disk/label_property_index.hpp | 71 + src/storage/v2/disk/name_id_mapper.hpp | 111 + src/storage/v2/disk/rocksdb_storage.cpp | 83 + src/storage/v2/disk/rocksdb_storage.hpp | 89 + src/storage/v2/disk/storage.cpp | 1703 +++++ src/storage/v2/disk/storage.hpp | 303 + src/storage/v2/disk/unique_constraints.cpp | 349 + src/storage/v2/disk/unique_constraints.hpp | 78 + src/storage/v2/durability/durability.cpp | 25 +- src/storage/v2/durability/durability.hpp | 4 +- src/storage/v2/durability/snapshot.cpp | 9 +- src/storage/v2/durability/snapshot.hpp | 4 +- src/storage/v2/durability/wal.cpp | 5 +- src/storage/v2/edge.hpp | 5 +- src/storage/v2/edge_accessor.cpp | 5 + src/storage/v2/edge_accessor.hpp | 1 - src/storage/v2/indices.cpp | 907 --- src/storage/v2/indices.hpp | 317 - src/storage/v2/indices/indices.cpp | 38 + src/storage/v2/indices/indices.hpp | 68 + src/storage/v2/indices/label_index.hpp | 51 + .../v2/indices/label_property_index.hpp | 59 + src/storage/v2/inmemory/indices_utils.hpp | 357 + src/storage/v2/inmemory/label_index.cpp | 192 + src/storage/v2/inmemory/label_index.hpp | 117 + .../v2/inmemory/label_property_index.cpp | 424 ++ .../v2/inmemory/label_property_index.hpp | 141 + src/storage/v2/inmemory/storage.cpp | 2075 ++++++ src/storage/v2/inmemory/storage.hpp | 522 ++ .../unique_constraints.cpp} | 83 +- .../v2/inmemory/unique_constraints.hpp | 101 + src/storage/v2/mvcc.hpp | 20 +- src/storage/v2/name_id_mapper.hpp | 41 +- src/storage/v2/property_store.cpp | 73 + src/storage/v2/property_store.hpp | 26 + src/storage/v2/property_value.hpp | 6 +- .../v2/replication/replication_client.cpp | 88 +- .../v2/replication/replication_client.hpp | 12 +- .../v2/replication/replication_server.cpp | 61 +- .../v2/replication/replication_server.hpp | 13 +- src/storage/v2/storage.cpp | 2382 +------ src/storage/v2/storage.hpp | 727 +-- src/storage/v2/storage_error.hpp | 26 +- src/storage/v2/storage_mode.cpp | 2 + src/storage/v2/storage_mode.hpp | 2 +- src/storage/v2/vertex.hpp | 5 +- src/storage/v2/vertex_accessor.cpp | 32 +- src/storage/v2/vertex_accessor.hpp | 3 +- src/storage/v2/vertices_iterable.cpp | 250 + src/storage/v2/vertices_iterable.hpp | 78 + src/utils/disk_utils.hpp | 28 + src/utils/logging.hpp | 13 +- src/utils/math.hpp | 8 +- src/utils/memory.hpp | 2 +- src/utils/rocksdb_serialization.hpp | 291 + src/utils/skip_list.hpp | 9 +- tests/benchmark/expansion.cpp | 26 +- tests/benchmark/query/eval.cpp | 13 +- tests/benchmark/query/execution.cpp | 108 +- tests/benchmark/query/planner.cpp | 28 +- tests/benchmark/storage_v2_gc.cpp | 19 +- tests/concurrent/storage_indices.cpp | 66 +- .../concurrent/storage_unique_constraints.cpp | 74 +- tests/e2e/CMakeLists.txt | 1 + tests/e2e/disk_storage/CMakeLists.txt | 14 + tests/e2e/disk_storage/common.py | 27 + .../disk_storage/create_edge_from_indices.py | 34 + tests/e2e/disk_storage/data_import.py | 45 + .../e2e/disk_storage/free_memory_disabled.py | 29 + .../disk_storage/lock_data_dir_disabled.py | 29 + .../e2e/disk_storage/replication_disabled.py | 29 + tests/e2e/disk_storage/snapshot_disabled.py | 29 + .../update_storage_mode_db_not_empty.py | 31 + .../update_storage_mode_disk_to_memory.py | 29 + .../update_storage_mode_memory_to_disk.py | 24 + tests/e2e/disk_storage/workloads.yaml | 53 + .../e2e/isolation_levels/isolation_levels.cpp | 60 +- tests/e2e/isolation_levels/workloads.yaml | 12 + tests/e2e/magic_functions/workloads.yaml | 13 + tests/e2e/memory/workloads.yaml | 8 + tests/e2e/mock_api/workloads.yaml | 83 + .../workloads.yaml | 13 + tests/e2e/run_e2e.sh | 4 + tests/e2e/runner.py | 7 + tests/e2e/temporal_types/workloads.yaml | 12 + tests/e2e/transaction_queue/workloads.yaml | 13 + tests/e2e/triggers/workloads.yaml | 39 + tests/e2e/write_procedures/workloads.yaml | 17 + tests/manual/interactive_planning.cpp | 1 - tests/manual/query_planner.cpp | 10 +- tests/manual/single_query.cpp | 11 +- tests/property_based/random_graph.cpp | 25 +- tests/unit/CMakeLists.txt | 20 +- tests/unit/auth_checker.cpp | 167 +- tests/unit/bfs_common.hpp | 830 +-- tests/unit/bfs_fine_grained.cpp | 82 +- tests/unit/bfs_single_node.cpp | 90 +- tests/unit/bolt_encoder.cpp | 58 +- tests/unit/clearing_old_disk_data.cpp | 181 + tests/unit/cpp_api.cpp | 64 +- tests/unit/disk_test_utils.hpp | 49 + tests/unit/interpreter.cpp | 648 +- tests/unit/interpreter_faker.hpp | 2 +- tests/unit/plan_pretty_print.cpp | 522 +- tests/unit/query_common.hpp | 157 +- tests/unit/query_cost_estimator.cpp | 17 +- tests/unit/query_dump.cpp | 482 +- tests/unit/query_expression_evaluator.cpp | 2294 ++++--- tests/unit/query_plan.cpp | 688 +- .../unit/query_plan_accumulate_aggregate.cpp | 413 +- tests/unit/query_plan_bag_semantics.cpp | 103 +- tests/unit/query_plan_common.hpp | 19 +- .../query_plan_create_set_remove_delete.cpp | 1159 ++-- tests/unit/query_plan_edge_cases.cpp | 51 +- tests/unit/query_plan_match_filter_return.cpp | 1878 +++--- .../unit/query_plan_read_write_typecheck.cpp | 205 +- ...query_plan_v2_create_set_remove_delete.cpp | 71 +- tests/unit/query_pretty_print.cpp | 69 +- tests/unit/query_procedure_mgp_type.cpp | 67 +- tests/unit/query_procedure_py_module.cpp | 87 +- tests/unit/query_procedures_mgp_graph.cpp | 356 +- tests/unit/query_required_privileges.cpp | 8 +- tests/unit/query_semantic.cpp | 348 +- tests/unit/query_streams.cpp | 165 +- tests/unit/query_trigger.cpp | 196 +- tests/unit/query_variable_start_planner.cpp | 164 +- tests/unit/storage_rocks.cpp | 119 + tests/unit/storage_test_utils.cpp | 11 + tests/unit/storage_v2.cpp | 1629 ++--- tests/unit/storage_v2_constraints.cpp | 985 +-- tests/unit/storage_v2_disk.cpp | 29 + ...cpp => storage_v2_durability_inmemory.cpp} | 989 +-- ..._edge.cpp => storage_v2_edge_inmemory.cpp} | 1198 ++-- tests/unit/storage_v2_edge_ondisk.cpp | 5815 +++++++++++++++++ tests/unit/storage_v2_gc.cpp | 76 +- tests/unit/storage_v2_indices.cpp | 937 ++- tests/unit/storage_v2_isolation_level.cpp | 79 +- tests/unit/storage_v2_property_store.cpp | 38 + tests/unit/storage_v2_replication.cpp | 709 +- tests/unit/storage_v2_storage_mode.cpp | 45 +- tests/unit/transaction_queue.cpp | 29 +- tests/unit/transaction_queue_multiple.cpp | 35 +- tests/unit/typed_value.cpp | 101 +- 182 files changed, 26406 insertions(+), 13468 deletions(-) create mode 100644 src/storage/README.md create mode 100644 src/storage/v2/all_vertices_iterable.cpp create mode 100644 src/storage/v2/all_vertices_iterable.hpp delete mode 100644 src/storage/v2/constraints.hpp create mode 100644 src/storage/v2/constraints/constraint_violation.hpp create mode 100644 src/storage/v2/constraints/constraints.hpp create mode 100644 src/storage/v2/constraints/existence_constraints.cpp create mode 100644 src/storage/v2/constraints/existence_constraints.hpp create mode 100644 src/storage/v2/constraints/unique_constraints.hpp create mode 100644 src/storage/v2/disk/label_index.cpp create mode 100644 src/storage/v2/disk/label_index.hpp create mode 100644 src/storage/v2/disk/label_property_index.cpp create mode 100644 src/storage/v2/disk/label_property_index.hpp create mode 100644 src/storage/v2/disk/name_id_mapper.hpp create mode 100644 src/storage/v2/disk/rocksdb_storage.cpp create mode 100644 src/storage/v2/disk/rocksdb_storage.hpp create mode 100644 src/storage/v2/disk/storage.cpp create mode 100644 src/storage/v2/disk/storage.hpp create mode 100644 src/storage/v2/disk/unique_constraints.cpp create mode 100644 src/storage/v2/disk/unique_constraints.hpp delete mode 100644 src/storage/v2/indices.cpp delete mode 100644 src/storage/v2/indices.hpp create mode 100644 src/storage/v2/indices/indices.cpp create mode 100644 src/storage/v2/indices/indices.hpp create mode 100644 src/storage/v2/indices/label_index.hpp create mode 100644 src/storage/v2/indices/label_property_index.hpp create mode 100644 src/storage/v2/inmemory/indices_utils.hpp create mode 100644 src/storage/v2/inmemory/label_index.cpp create mode 100644 src/storage/v2/inmemory/label_index.hpp create mode 100644 src/storage/v2/inmemory/label_property_index.cpp create mode 100644 src/storage/v2/inmemory/label_property_index.hpp create mode 100644 src/storage/v2/inmemory/storage.cpp create mode 100644 src/storage/v2/inmemory/storage.hpp rename src/storage/v2/{constraints.cpp => inmemory/unique_constraints.cpp} (80%) create mode 100644 src/storage/v2/inmemory/unique_constraints.hpp create mode 100644 src/storage/v2/vertices_iterable.cpp create mode 100644 src/storage/v2/vertices_iterable.hpp create mode 100644 src/utils/disk_utils.hpp create mode 100644 src/utils/rocksdb_serialization.hpp create mode 100644 tests/e2e/disk_storage/CMakeLists.txt create mode 100644 tests/e2e/disk_storage/common.py create mode 100644 tests/e2e/disk_storage/create_edge_from_indices.py create mode 100644 tests/e2e/disk_storage/data_import.py create mode 100644 tests/e2e/disk_storage/free_memory_disabled.py create mode 100644 tests/e2e/disk_storage/lock_data_dir_disabled.py create mode 100644 tests/e2e/disk_storage/replication_disabled.py create mode 100644 tests/e2e/disk_storage/snapshot_disabled.py create mode 100644 tests/e2e/disk_storage/update_storage_mode_db_not_empty.py create mode 100644 tests/e2e/disk_storage/update_storage_mode_disk_to_memory.py create mode 100644 tests/e2e/disk_storage/update_storage_mode_memory_to_disk.py create mode 100644 tests/e2e/disk_storage/workloads.yaml create mode 100644 tests/e2e/run_e2e.sh create mode 100644 tests/unit/clearing_old_disk_data.cpp create mode 100644 tests/unit/disk_test_utils.hpp create mode 100644 tests/unit/storage_rocks.cpp create mode 100644 tests/unit/storage_v2_disk.cpp rename tests/unit/{storage_v2_durability.cpp => storage_v2_durability_inmemory.cpp} (70%) rename tests/unit/{storage_v2_edge.cpp => storage_v2_edge_inmemory.cpp} (86%) create mode 100644 tests/unit/storage_v2_edge_ondisk.cpp diff --git a/include/mg_procedure.h b/include/mg_procedure.h index 8f4888d57..f92b0cf16 100644 --- a/include/mg_procedure.h +++ b/include/mg_procedure.h @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/http_handlers/metrics.hpp b/src/http_handlers/metrics.hpp index dc3315948..a83f6ee06 100644 --- a/src/http_handlers/metrics.hpp +++ b/src/http_handlers/metrics.hpp @@ -50,7 +50,7 @@ struct MetricsResponse { template <typename TSessionData> class MetricsService { public: - explicit MetricsService(TSessionData *data) : db_(data->db) {} + explicit MetricsService(TSessionData *data) : db_(data->interpreter_context->db.get()) {} nlohmann::json GetMetricsJSON() { auto response = GetMetrics(); diff --git a/src/memgraph.cpp b/src/memgraph.cpp index 6d730a137..4d68afa61 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -56,6 +56,9 @@ #include "query/procedure/module.hpp" #include "query/procedure/py_module.hpp" #include "requests/requests.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/view.hpp" @@ -443,21 +446,19 @@ struct SessionData { // supplied. #if MG_ENTERPRISE - SessionData(memgraph::storage::Storage *db, memgraph::query::InterpreterContext *interpreter_context, + SessionData(memgraph::query::InterpreterContext *interpreter_context, memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, memgraph::audit::Log *audit_log) - : db(db), interpreter_context(interpreter_context), auth(auth), audit_log(audit_log) {} - memgraph::storage::Storage *db; + : interpreter_context(interpreter_context), auth(auth), audit_log(audit_log) {} memgraph::query::InterpreterContext *interpreter_context; memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth; memgraph::audit::Log *audit_log; #else - SessionData(memgraph::storage::Storage *db, memgraph::query::InterpreterContext *interpreter_context, + SessionData(memgraph::query::InterpreterContext *interpreter_context, memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth) - : db(db), interpreter_context(interpreter_context), auth(auth) {} - memgraph::storage::Storage *db; + : interpreter_context(interpreter_context), auth(auth) {} memgraph::query::InterpreterContext *interpreter_context; memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth; @@ -508,7 +509,6 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph memgraph::communication::v2::OutputStream *output_stream) : memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream, memgraph::communication::v2::OutputStream>(input_stream, output_stream), - db_(data->db), interpreter_context_(data->interpreter_context), interpreter_(data->interpreter_context), auth_(data->auth), @@ -583,7 +583,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph std::map<std::string, memgraph::communication::bolt::Value> Pull(TEncoder *encoder, std::optional<int> n, std::optional<int> qid) override { - TypedValueResultStream stream(encoder, db_); + TypedValueResultStream stream(encoder, interpreter_context_->db.get()); return PullResults(stream, n, qid); } @@ -617,7 +617,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph const auto &summary = interpreter_.Pull(&stream, n, qid); std::map<std::string, memgraph::communication::bolt::Value> decoded_summary; for (const auto &kv : summary) { - auto maybe_value = memgraph::glue::ToBoltValue(kv.second, *db_, memgraph::storage::View::NEW); + auto maybe_value = + memgraph::glue::ToBoltValue(kv.second, *interpreter_context_->db, memgraph::storage::View::NEW); if (maybe_value.HasError()) { switch (maybe_value.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -681,7 +682,6 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph }; // NOTE: Needed only for ToBoltValue conversions - const memgraph::storage::Storage *db_; memgraph::query::InterpreterContext *interpreter_context_; memgraph::query::Interpreter interpreter_; memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_; @@ -895,7 +895,15 @@ int main(int argc, char **argv) { .items_per_batch = FLAGS_storage_items_per_batch, .recovery_thread_count = FLAGS_storage_recovery_thread_count, .allow_parallel_index_creation = FLAGS_storage_parallel_index_recovery}, - .transaction = {.isolation_level = ParseIsolationLevel()}}; + .transaction = {.isolation_level = ParseIsolationLevel()}, + .disk = {.main_storage_directory = FLAGS_data_directory + "/rocksdb_main_storage", + .label_index_directory = FLAGS_data_directory + "/rocksdb_label_index", + .label_property_index_directory = FLAGS_data_directory + "/rocksdb_label_property_index", + .unique_constraints_directory = FLAGS_data_directory + "/rocksdb_unique_constraints", + .name_id_mapper_directory = FLAGS_data_directory + "/rocksdb_name_id_mapper", + .id_name_mapper_directory = FLAGS_data_directory + "/rocksdb_id_name_mapper", + .durability_directory = FLAGS_data_directory + "/rocksdb_durability", + .wal_directory = FLAGS_data_directory + "/rocksdb_wal"}}; if (FLAGS_storage_snapshot_interval_sec == 0) { if (FLAGS_storage_wal_enabled) { LOG_FATAL( @@ -914,10 +922,9 @@ int main(int argc, char **argv) { } db_config.durability.snapshot_interval = std::chrono::seconds(FLAGS_storage_snapshot_interval_sec); } - memgraph::storage::Storage db(db_config); memgraph::query::InterpreterContext interpreter_context{ - &db, + db_config, {.query = {.allow_load_csv = FLAGS_allow_load_csv}, .execution_timeout_sec = FLAGS_query_execution_timeout_sec, .replication_replica_check_frequency = std::chrono::seconds(FLAGS_replication_replica_check_frequency_sec), @@ -927,9 +934,9 @@ int main(int argc, char **argv) { .stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)}, FLAGS_data_directory}; #ifdef MG_ENTERPRISE - SessionData session_data{&db, &interpreter_context, &auth, &audit_log}; + SessionData session_data{&interpreter_context, &auth, &audit_log}; #else - SessionData session_data{&db, &interpreter_context, &auth}; + SessionData session_data{&interpreter_context, &auth}; #endif memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory); @@ -969,7 +976,7 @@ int main(int argc, char **argv) { // Triggers can execute query procedures, so we need to reload the modules first and then // the triggers auto storage_accessor = interpreter_context.db->Access(); - auto dba = memgraph::query::DbAccessor{&storage_accessor}; + auto dba = memgraph::query::DbAccessor{storage_accessor.get()}; interpreter_context.trigger_store.RestoreTriggers( &interpreter_context.ast_cache, &dba, interpreter_context.config.query, interpreter_context.auth_checker); } @@ -1002,8 +1009,8 @@ int main(int argc, char **argv) { std::optional<memgraph::telemetry::Telemetry> telemetry; if (FLAGS_telemetry_enabled) { telemetry.emplace(telemetry_server, data_directory / "telemetry", run_id, machine_id, std::chrono::minutes(10)); - telemetry->AddCollector("storage", [&db]() -> nlohmann::json { - auto info = db.GetInfo(); + telemetry->AddCollector("storage", [db_ = interpreter_context.db.get()]() -> nlohmann::json { + auto info = db_->GetInfo(); return {{"vertices", info.vertex_count}, {"edges", info.edge_count}}; }); telemetry->AddCollector("event_counters", []() -> nlohmann::json { diff --git a/src/mg_import_csv.cpp b/src/mg_import_csv.cpp index 3b9717ae6..92ce0fa68 100644 --- a/src/mg_import_csv.cpp +++ b/src/mg_import_csv.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -20,7 +20,8 @@ #include <unordered_map> #include "helpers.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "utils/exceptions.hpp" #include "utils/logging.hpp" #include "utils/message.hpp" @@ -421,7 +422,7 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st std::unordered_map<NodeId, memgraph::storage::Gid> *node_id_map) { std::optional<NodeId> id; auto acc = store->Access(); - auto node = acc.CreateVertex(); + auto node = acc->CreateVertex(); for (size_t i = 0; i < row.size(); ++i) { const auto &field = fields[i]; const auto &value = row[i]; @@ -450,29 +451,29 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st } else { pv_id = memgraph::storage::PropertyValue(node_id.id); } - auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), pv_id); + auto old_node_property = node.SetProperty(acc->NameToProperty(field.name), pv_id); if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name); if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name); } id = node_id; } else if (field.type == "LABEL") { for (const auto &label : memgraph::utils::Split(value, FLAGS_array_delimiter)) { - auto node_label = node.AddLabel(acc.NameToLabel(label)); + auto node_label = node.AddLabel(acc->NameToLabel(label)); if (!node_label.HasValue()) throw LoadException("Couldn't add label '{}' to the node", label); if (!*node_label) throw LoadException("The label '{}' already exists", label); } } else if (field.type != "IGNORE") { - auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), StringToValue(value, field.type)); + auto old_node_property = node.SetProperty(acc->NameToProperty(field.name), StringToValue(value, field.type)); if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name); if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name); } } for (const auto &label : additional_labels) { - auto node_label = node.AddLabel(acc.NameToLabel(label)); + auto node_label = node.AddLabel(acc->NameToLabel(label)); if (!node_label.HasValue()) throw LoadException("Couldn't add label '{}' to the node", label); if (!*node_label) throw LoadException("The label '{}' already exists", label); } - if (acc.Commit().HasError()) throw LoadException("Couldn't store the node"); + if (acc->Commit().HasError()) throw LoadException("Couldn't store the node"); } void ProcessNodes(memgraph::storage::Storage *store, const std::string &nodes_path, @@ -567,16 +568,16 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto if (!relationship_type) throw LoadException("Relationship TYPE must be set"); auto acc = store->Access(); - auto from_node = acc.FindVertex(*start_id, memgraph::storage::View::NEW); + auto from_node = acc->FindVertex(*start_id, memgraph::storage::View::NEW); if (!from_node) throw LoadException("From node must be in the storage"); - auto to_node = acc.FindVertex(*end_id, memgraph::storage::View::NEW); + auto to_node = acc->FindVertex(*end_id, memgraph::storage::View::NEW); if (!to_node) throw LoadException("To node must be in the storage"); - auto relationship = acc.CreateEdge(&*from_node, &*to_node, acc.NameToEdgeType(*relationship_type)); + auto relationship = acc->CreateEdge(&from_node.value(), &to_node.value(), acc->NameToEdgeType(*relationship_type)); if (!relationship.HasValue()) throw LoadException("Couldn't create the relationship"); for (const auto &property : properties) { - auto ret = relationship->SetProperty(acc.NameToProperty(property.first), property.second); + auto ret = relationship.GetValue().SetProperty(acc->NameToProperty(property.first), property.second); if (!ret.HasValue()) { if (ret.GetError() != memgraph::storage::Error::PROPERTIES_DISABLED) { throw LoadException("Couldn't add property '{}' to the relationship", property.first); @@ -589,7 +590,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto } } - if (acc.Commit().HasError()) throw LoadException("Couldn't store the relationship"); + if (acc->Commit().HasError()) throw LoadException("Couldn't store the relationship"); } void ProcessRelationships(memgraph::storage::Storage *store, const std::string &relationships_path, @@ -699,13 +700,13 @@ int main(int argc, char *argv[]) { } std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map; - memgraph::storage::Storage store{{ + std::unique_ptr<memgraph::storage::Storage> store{new memgraph::storage::InMemoryStorage{{ .items = {.properties_on_edges = FLAGS_storage_properties_on_edges}, .durability = {.storage_directory = FLAGS_data_directory, .recover_on_startup = false, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED, .snapshot_on_exit = true}, - }}; + }}}; memgraph::utils::Timer load_timer; @@ -715,7 +716,7 @@ int main(int argc, char *argv[]) { std::optional<std::vector<Field>> header; for (const auto &nodes_file : files) { spdlog::info("Loading {}", nodes_file); - ProcessNodes(&store, nodes_file, &header, &node_id_map, additional_labels); + ProcessNodes(store.get(), nodes_file, &header, &node_id_map, additional_labels); } } @@ -725,7 +726,7 @@ int main(int argc, char *argv[]) { std::optional<std::vector<Field>> header; for (const auto &relationships_file : files) { spdlog::info("Loading {}", relationships_file); - ProcessRelationships(&store, relationships_file, type, &header, node_id_map); + ProcessRelationships(store.get(), relationships_file, type, &header, node_id_map); } } diff --git a/src/query/db_accessor.cpp b/src/query/db_accessor.cpp index 48034cdff..80f0b3e88 100644 --- a/src/query/db_accessor.cpp +++ b/src/query/db_accessor.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index b80fb0fe3..b5414be73 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -17,9 +17,11 @@ #include <cppitertools/imap.hpp> #include "query/exceptions.hpp" +#include "storage/v2/edge_accessor.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/result.hpp" +#include "storage/v2/storage_mode.hpp" #include "utils/pmr/unordered_set.hpp" #include "utils/variant_helpers.hpp" @@ -347,6 +349,10 @@ class DbAccessor final { VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); } + void PrefetchOutEdges(const VertexAccessor &vertex) const { accessor_->PrefetchOutEdges(vertex.impl_); } + + void PrefetchInEdges(const VertexAccessor &vertex) const { accessor_->PrefetchInEdges(vertex.impl_); } + storage::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to, const storage::EdgeTypeId &edge_type) { auto maybe_edge = accessor_->CreateEdge(&from->impl_, &to->impl_, edge_type); @@ -372,6 +378,8 @@ class DbAccessor final { VertexAccessor *vertex_accessor) { using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>; + accessor_->PrefetchOutEdges(vertex_accessor->impl_); + accessor_->PrefetchInEdges(vertex_accessor->impl_); auto res = accessor_->DetachDeleteVertex(&vertex_accessor->impl_); if (res.HasError()) { return res.GetError(); @@ -424,6 +432,8 @@ class DbAccessor final { void Abort() { accessor_->Abort(); } + storage::StorageMode GetStorageMode() const { return accessor_->GetCreationStorageMode(); } + bool LabelIndexExists(storage::LabelId label) const { return accessor_->LabelIndexExists(label); } bool LabelPropertyIndexExists(storage::LabelId label, storage::PropertyId prop) const { @@ -508,6 +518,10 @@ class SubgraphDbAccessor final { const std::string &EdgeTypeToName(storage::EdgeTypeId type) const; + void PrefetchOutEdges(const SubgraphVertexAccessor &vertex) const { db_accessor_.PrefetchOutEdges(vertex.impl_); } + + void PrefetchInEdges(const SubgraphVertexAccessor &vertex) const { db_accessor_.PrefetchInEdges(vertex.impl_); } + storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge); storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to, diff --git a/src/query/dump.cpp b/src/query/dump.cpp index 8421e4376..c668b460c 100644 --- a/src/query/dump.cpp +++ b/src/query/dump.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -482,6 +482,7 @@ PullPlanDump::PullChunk PullPlanDump::CreateEdgePullChunk() { // If we have a saved iterable from a previous pull // we need to use the same iterable if (!maybe_edge_iterable) { + dba_->PrefetchOutEdges(vertex); maybe_edge_iterable = std::make_shared<EdgeAccessorIterable>(vertex.OutEdges(storage::View::OLD)); } auto &maybe_edges = *maybe_edge_iterable; diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp index 874a102f6..9b5c6651e 100644 --- a/src/query/exceptions.hpp +++ b/src/query/exceptions.hpp @@ -175,18 +175,34 @@ class ReplicationModificationInMulticommandTxException : public QueryException { : QueryException("Replication clause not allowed in multicommand transactions.") {} }; +class ReplicationDisabledOnDiskStorage : public QueryException { + public: + ReplicationDisabledOnDiskStorage() : QueryException("Replication not support with disk storage. ") {} +}; + class LockPathModificationInMulticommandTxException : public QueryException { public: LockPathModificationInMulticommandTxException() : QueryException("Lock path query not allowed in multicommand transactions.") {} }; +class LockPathDisabledOnDiskStorage : public QueryException { + public: + LockPathDisabledOnDiskStorage() + : QueryException("Lock path disabled on disk storage since all data is already persisted. ") {} +}; + class FreeMemoryModificationInMulticommandTxException : public QueryException { public: FreeMemoryModificationInMulticommandTxException() : QueryException("Free memory query not allowed in multicommand transactions.") {} }; +class FreeMemoryDisabledOnDiskStorage : public QueryException { + public: + FreeMemoryDisabledOnDiskStorage() : QueryException("Free memory does nothing when using disk storage. ") {} +}; + class ShowConfigModificationInMulticommandTxException : public QueryException { public: ShowConfigModificationInMulticommandTxException() @@ -232,6 +248,11 @@ class CreateSnapshotInMulticommandTxException final : public QueryException { : QueryException("Snapshot cannot be created in multicommand transactions.") {} }; +class CreateSnapshotDisabledOnDiskStorage final : public QueryException { + public: + CreateSnapshotDisabledOnDiskStorage() : QueryException("Data is already persisted when using disk storage. ") {} +}; + class SettingConfigInMulticommandTxException final : public QueryException { public: SettingConfigInMulticommandTxException() @@ -264,4 +285,14 @@ class TransactionQueueInMulticommandTxException : public QueryException { : QueryException("Transaction queue queries not allowed in multicommand transactions.") {} }; +class IndexPersistenceException : public QueryException { + public: + IndexPersistenceException() : QueryException("Persisting index on disk failed.") {} +}; + +class ConstraintsPersistenceException : public QueryException { + public: + ConstraintsPersistenceException() : QueryException("Persisting constraints on disk failed.") {} +}; + } // namespace memgraph::query diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index caee103a2..e30ffdb82 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -3143,7 +3143,7 @@ class StorageModeQuery : public memgraph::query::Query { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } - enum class StorageMode { IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL }; + enum class StorageMode { IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL, ON_DISK_TRANSACTIONAL }; StorageModeQuery() = default; diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index f2e037172..ef0a0aad9 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -504,7 +504,10 @@ antlrcpp::Any CypherMainVisitor::visitStorageModeQuery(MemgraphCypher::StorageMo if (mode->IN_MEMORY_ANALYTICAL()) { return StorageModeQuery::StorageMode::IN_MEMORY_ANALYTICAL; } - return StorageModeQuery::StorageMode::IN_MEMORY_TRANSACTIONAL; + if (mode->IN_MEMORY_TRANSACTIONAL()) { + return StorageModeQuery::StorageMode::IN_MEMORY_TRANSACTIONAL; + } + return StorageModeQuery::StorageMode::ON_DISK_TRANSACTIONAL; }); query_ = storage_mode_query; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index f7ffe8803..a55d1281a 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -365,7 +365,7 @@ isolationLevelScope : GLOBAL | SESSION | NEXT ; isolationLevelQuery : SET isolationLevelScope TRANSACTION ISOLATION LEVEL isolationLevel ; -storageMode : IN_MEMORY_ANALYTICAL | IN_MEMORY_TRANSACTIONAL ; +storageMode : IN_MEMORY_ANALYTICAL | IN_MEMORY_TRANSACTIONAL | ON_DISK_TRANSACTIONAL ; storageModeQuery : STORAGE MODE storageMode ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 index 674a5f61d..149e637b1 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 @@ -85,6 +85,7 @@ MODULE_WRITE : M O D U L E UNDERSCORE W R I T E ; NEXT : N E X T ; NO : N O ; NOTHING : N O T H I N G ; +ON_DISK_TRANSACTIONAL : O N UNDERSCORE D I S K UNDERSCORE T R A N S A C T I O N A L ; NULLIF : N U L L I F ; PASSWORD : P A S S W O R D ; PORT : P O R T ; diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index 21d045601..a90aa70aa 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -467,6 +467,8 @@ TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext & FType<Or<Null, Vertex>>("degree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); + ctx.db_accessor->PrefetchInEdges(vertex); + ctx.db_accessor->PrefetchOutEdges(vertex); size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view)); size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view)); return TypedValue(static_cast<int64_t>(out_degree + in_degree), ctx.memory); @@ -476,6 +478,7 @@ TypedValue InDegree(const TypedValue *args, int64_t nargs, const FunctionContext FType<Or<Null, Vertex>>("inDegree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); + ctx.db_accessor->PrefetchInEdges(vertex); size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view)); return TypedValue(static_cast<int64_t>(in_degree), ctx.memory); } @@ -484,6 +487,7 @@ TypedValue OutDegree(const TypedValue *args, int64_t nargs, const FunctionContex FType<Or<Null, Vertex>>("outDegree", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); const auto &vertex = args[0].ValueVertex(); + ctx.db_accessor->PrefetchOutEdges(vertex); size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view)); return TypedValue(static_cast<int64_t>(out_degree), ctx.memory); } diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 087fdb09a..8b95b5ee2 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -58,21 +58,25 @@ #include "query/trigger.hpp" #include "query/typed_value.hpp" #include "spdlog/spdlog.h" +#include "storage/v2/disk/storage.hpp" #include "storage/v2/edge.hpp" #include "storage/v2/id_types.hpp" -#include "storage/v2/isolation_level.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/property_value.hpp" +#include "storage/v2/replication/config.hpp" #include "storage/v2/storage_mode.hpp" #include "utils/algorithm.hpp" #include "utils/build_info.hpp" #include "utils/event_counter.hpp" #include "utils/event_histogram.hpp" #include "utils/exceptions.hpp" +#include "utils/file.hpp" #include "utils/flag_validation.hpp" #include "utils/likely.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" #include "utils/memory_tracker.hpp" +#include "utils/message.hpp" #include "utils/on_scope_exit.hpp" #include "utils/readable_size.hpp" #include "utils/settings.hpp" @@ -167,8 +171,9 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { /// @throw QueryRuntimeException if an error ocurred. void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(db_); if (replication_role == ReplicationQuery::ReplicationRole::MAIN) { - if (!db_->SetMainReplicationRole()) { + if (!mem_storage->SetMainReplicationRole()) { throw QueryRuntimeException("Couldn't set role to main!"); } } @@ -176,8 +181,9 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { if (!port || *port < 0 || *port > std::numeric_limits<uint16_t>::max()) { throw QueryRuntimeException("Port number invalid!"); } - if (!db_->SetReplicaRole( - io::network::Endpoint(storage::replication::kDefaultReplicationServerIp, static_cast<uint16_t>(*port)))) { + if (!mem_storage->SetReplicaRole( + io::network::Endpoint(storage::replication::kDefaultReplicationServerIp, static_cast<uint16_t>(*port)), + storage::replication::ReplicationServerConfig{})) { throw QueryRuntimeException("Couldn't set role to replica!"); } } @@ -185,7 +191,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { /// @throw QueryRuntimeException if an error ocurred. ReplicationQuery::ReplicationRole ShowReplicationRole() const override { - switch (db_->GetReplicationRole()) { + switch (static_cast<storage::InMemoryStorage *>(db_)->GetReplicationRole()) { case storage::replication::ReplicationRole::MAIN: return ReplicationQuery::ReplicationRole::MAIN; case storage::replication::ReplicationRole::REPLICA: @@ -198,7 +204,8 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { void RegisterReplica(const std::string &name, const std::string &socket_address, const ReplicationQuery::SyncMode sync_mode, const std::chrono::seconds replica_check_frequency) override { - if (db_->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(db_); + if (mem_storage->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { // replica can't register another replica throw QueryRuntimeException("Replica can't register another replica!"); } @@ -223,9 +230,9 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { io::network::Endpoint::ParseSocketOrIpAddress(socket_address, storage::replication::kDefaultReplicationPort); if (maybe_ip_and_port) { auto [ip, port] = *maybe_ip_and_port; - auto ret = db_->RegisterReplica(name, {std::move(ip), port}, repl_mode, - storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, - {.replica_check_frequency = replica_check_frequency, .ssl = std::nullopt}); + auto ret = mem_storage->RegisterReplica( + name, {std::move(ip), port}, repl_mode, storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + {.replica_check_frequency = replica_check_frequency, .ssl = std::nullopt}); if (ret.HasError()) { throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name)); } @@ -236,23 +243,26 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { /// @throw QueryRuntimeException if an error ocurred. void DropReplica(const std::string &replica_name) override { - if (db_->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(db_); + + if (mem_storage->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { // replica can't unregister a replica throw QueryRuntimeException("Replica can't unregister a replica!"); } - if (!db_->UnregisterReplica(replica_name)) { + if (!mem_storage->UnregisterReplica(replica_name)) { throw QueryRuntimeException(fmt::format("Couldn't unregister the replica '{}'", replica_name)); } } using Replica = ReplicationQueryHandler::Replica; std::vector<Replica> ShowReplicas() const override { - if (db_->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(db_); + if (mem_storage->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) { // replica can't show registered replicas (it shouldn't have any) throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!"); } - auto repl_infos = db_->ReplicasInfo(); + auto repl_infos = mem_storage->ReplicasInfo(); std::vector<Replica> replicas; replicas.reserve(repl_infos.size()); @@ -534,7 +544,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & notifications->emplace_back(SeverityLevel::WARNING, NotificationCode::REPLICA_PORT_WARNING, "Be careful the replication port must be different from the memgraph port!"); } - callback.fn = [handler = ReplQueryHandler{interpreter_context->db}, role = repl_query->role_, + callback.fn = [handler = ReplQueryHandler{interpreter_context->db.get()}, role = repl_query->role_, maybe_port]() mutable { handler.SetReplicationRole(role, maybe_port); return std::vector<std::vector<TypedValue>>(); @@ -547,7 +557,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & } case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: { callback.header = {"replication role"}; - callback.fn = [handler = ReplQueryHandler{interpreter_context->db}] { + callback.fn = [handler = ReplQueryHandler{interpreter_context->db.get()}] { auto mode = handler.ShowReplicationRole(); switch (mode) { case ReplicationQuery::ReplicationRole::MAIN: { @@ -566,7 +576,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & auto socket_address = repl_query->socket_address_->Accept(evaluator); const auto replica_check_frequency = interpreter_context->config.replication_replica_check_frequency; - callback.fn = [handler = ReplQueryHandler{interpreter_context->db}, name, socket_address, sync_mode, + callback.fn = [handler = ReplQueryHandler{interpreter_context->db.get()}, name, socket_address, sync_mode, replica_check_frequency]() mutable { handler.RegisterReplica(name, std::string(socket_address.ValueString()), sync_mode, replica_check_frequency); return std::vector<std::vector<TypedValue>>(); @@ -578,7 +588,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & case ReplicationQuery::Action::DROP_REPLICA: { const auto &name = repl_query->replica_name_; - callback.fn = [handler = ReplQueryHandler{interpreter_context->db}, name]() mutable { + callback.fn = [handler = ReplQueryHandler{interpreter_context->db.get()}, name]() mutable { handler.DropReplica(name); return std::vector<std::vector<TypedValue>>(); }; @@ -591,7 +601,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & callback.header = { "name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master", "state"}; - callback.fn = [handler = ReplQueryHandler{interpreter_context->db}, replica_nfields = callback.header.size()] { + callback.fn = [handler = ReplQueryHandler{interpreter_context->db.get()}, + replica_nfields = callback.header.size()] { const auto &replicas = handler.ShowReplicas(); auto typed_replicas = std::vector<std::vector<TypedValue>>{}; typed_replicas.reserve(replicas.size()); @@ -1188,11 +1199,38 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea } using RWType = plan::ReadWriteTypeChecker::RWType; + +bool IsWriteQueryOnMainMemoryReplica(storage::Storage *storage, + const query::plan::ReadWriteTypeChecker::RWType query_type) { + if (auto storage_mode = storage->GetStorageMode(); storage_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL || + storage_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL) { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(storage); + return (mem_storage->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA) && + (query_type == RWType::W || query_type == RWType::RW); + } + return false; +} + } // namespace -InterpreterContext::InterpreterContext(storage::Storage *db, const InterpreterConfig config, +InterpreterContext::InterpreterContext(const storage::Config storage_config, const InterpreterConfig interpreter_config, const std::filesystem::path &data_directory) - : db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory / "streams"} {} + : trigger_store(data_directory / "triggers"), + config(interpreter_config), + streams{this, data_directory / "streams"} { + if (utils::DirExists(storage_config.disk.main_storage_directory)) { + db = std::make_unique<storage::DiskStorage>(storage_config); + } else { + db = std::make_unique<storage::InMemoryStorage>(storage_config); + } +} + +InterpreterContext::InterpreterContext(std::unique_ptr<storage::Storage> db, InterpreterConfig interpreter_config, + const std::filesystem::path &data_directory) + : db(std::move(db)), + trigger_store(data_directory / "triggers"), + config(interpreter_config), + streams{this, data_directory / "streams"} {} Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) { MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL"); @@ -1216,8 +1254,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper, expect_rollback_ = false; metadata_ = GenOptional(metadata); - db_accessor_ = - std::make_unique<storage::Storage::Accessor>(interpreter_context_->db->Access(GetIsolationLevelOverride())); + db_accessor_ = interpreter_context_->db->Access(GetIsolationLevelOverride()); execution_db_accessor_.emplace(db_accessor_.get()); transaction_status_.store(TransactionStatus::ACTIVE, std::memory_order_release); @@ -1845,6 +1882,8 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans index_notification.code = NotificationCode::EXISTENT_INDEX; index_notification.title = fmt::format("Index on label {} on properties {} already exists.", label_name, properties_stringified); + } else if constexpr (std::is_same_v<ErrorType, storage::IndexPersistenceError>) { + throw IndexPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -1880,6 +1919,8 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans index_notification.code = NotificationCode::NONEXISTENT_INDEX; index_notification.title = fmt::format("Index on label {} on properties {} doesn't exist.", label_name, properties_stringified); + } else if constexpr (std::is_same_v<ErrorType, storage::IndexPersistenceError>) { + throw IndexPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -1947,6 +1988,10 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit throw ReplicationModificationInMulticommandTxException(); } + if (interpreter_context->db->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + throw ReplicationDisabledOnDiskStorage(); + } + auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query); auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, interpreter_context, dba, notifications); @@ -1969,11 +2014,15 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit } PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_transaction, - InterpreterContext *interpreter_context, DbAccessor *dba) { + InterpreterContext *interpreter_context) { if (in_explicit_transaction) { throw LockPathModificationInMulticommandTxException(); } + if (interpreter_context->db->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + throw LockPathDisabledOnDiskStorage(); + } + auto *lock_path_query = utils::Downcast<LockPathQuery>(parsed_query.query); return PreparedQuery{ @@ -1981,12 +2030,13 @@ PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_tr std::move(parsed_query.required_privileges), [interpreter_context, action = lock_path_query->action_]( AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(interpreter_context->db.get()); std::vector<std::vector<TypedValue>> status; std::string res; switch (action) { case LockPathQuery::Action::LOCK_PATH: { - const auto lock_success = interpreter_context->db->LockPath(); + const auto lock_success = mem_storage->LockPath(); if (lock_success.HasError()) [[unlikely]] { throw QueryRuntimeException("Failed to lock the data directory"); } @@ -1994,7 +2044,7 @@ PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_tr break; } case LockPathQuery::Action::UNLOCK_PATH: { - const auto unlock_success = interpreter_context->db->UnlockPath(); + const auto unlock_success = mem_storage->UnlockPath(); if (unlock_success.HasError()) [[unlikely]] { throw QueryRuntimeException("Failed to unlock the data directory"); } @@ -2002,7 +2052,7 @@ PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_tr break; } case LockPathQuery::Action::STATUS: { - const auto locked_status = interpreter_context->db->IsPathLocked(); + const auto locked_status = mem_storage->IsPathLocked(); if (locked_status.HasError()) [[unlikely]] { throw QueryRuntimeException("Failed to access the data directory"); } @@ -2027,6 +2077,10 @@ PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, bool in_explicit_ throw FreeMemoryModificationInMulticommandTxException(); } + if (interpreter_context->db->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + throw FreeMemoryDisabledOnDiskStorage(); + } + return PreparedQuery{ {}, std::move(parsed_query.required_privileges), @@ -2240,9 +2294,23 @@ constexpr auto ToStorageMode(const StorageModeQuery::StorageMode storage_mode) n return storage::StorageMode::IN_MEMORY_TRANSACTIONAL; case StorageModeQuery::StorageMode::IN_MEMORY_ANALYTICAL: return storage::StorageMode::IN_MEMORY_ANALYTICAL; + case StorageModeQuery::StorageMode::ON_DISK_TRANSACTIONAL: + return storage::StorageMode::ON_DISK_TRANSACTIONAL; } } +bool SwitchingFromInMemoryToDisk(storage::StorageMode current_mode, storage::StorageMode next_mode) { + return (current_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL || + current_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL) && + next_mode == storage::StorageMode::ON_DISK_TRANSACTIONAL; +} + +bool SwitchingFromDiskToInMemory(storage::StorageMode current_mode, storage::StorageMode next_mode) { + return current_mode == storage::StorageMode::ON_DISK_TRANSACTIONAL && + (next_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL || + next_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL); +} + PreparedQuery PrepareIsolationLevelQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, InterpreterContext *interpreter_context, Interpreter *interpreter) { if (in_explicit_transaction) { @@ -2284,6 +2352,59 @@ PreparedQuery PrepareIsolationLevelQuery(ParsedQuery parsed_query, const bool in RWType::NONE}; } +Callback SwitchMemoryDevice(storage::StorageMode current_mode, storage::StorageMode requested_mode, + InterpreterContext *interpreter_context) { + Callback callback; + callback.fn = [current_mode, requested_mode, interpreter_context]() mutable { + if (current_mode == requested_mode) { + return std::vector<std::vector<TypedValue>>(); + } + if (SwitchingFromDiskToInMemory(current_mode, requested_mode)) { + throw utils::BasicException( + "You cannot switch from on-disk storage to in-memory storage while the database is running. " + "Please delete your data directory and restart the database. Once restarted, the Memgraph will automatically " + "start to use in-memory storage."); + } + if (SwitchingFromInMemoryToDisk(current_mode, requested_mode)) { + std::unique_lock main_guard{interpreter_context->db->main_lock_}; + + if (auto vertex_cnt_approx = interpreter_context->db->GetInfo().vertex_count; vertex_cnt_approx > 0) { + throw utils::BasicException( + "You cannot switch from in-memory storage to on-disk storage when the database " + "contains data. Please delete all entries from the database, run FREE MEMORY and then repeat this " + "query. "); + } + + main_guard.unlock(); + if (interpreter_context->interpreters->size() > 1) { + throw utils::BasicException( + "You cannot switch from in-memory storage to on-disk storage when there are " + "multiple sessions active. Please close all other sessions and try again. If you are using Memgraph Lab, " + "please start mgconsole " + "and run the STORAGE MODE ON_DISK_TRANSACTIONAL there first. Memgraph Lab is using multiple sessions to " + "run queries in parallel " + "so it is currently impossible to switch to on-disk storage while the Lab is running. After you switch " + "from the mgconsole, you can " + "continue to use Memgraph Lab as you wish."); + } + + auto db_config = interpreter_context->db->config_; + interpreter_context->db = std::make_unique<memgraph::storage::DiskStorage>(db_config); + } + return std::vector<std::vector<TypedValue>>(); + }; + return callback; +} + +bool ActiveTransactionsExist(InterpreterContext *interpreter_context) { + bool exists_active_transaction = interpreter_context->interpreters.WithLock([](const auto &interpreters_) { + return std::any_of(interpreters_.begin(), interpreters_.end(), [](const auto &interpreter) { + return interpreter->transaction_status_.load() != TransactionStatus::IDLE; + }); + }); + return exists_active_transaction; +} + PreparedQuery PrepareStorageModeQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, InterpreterContext *interpreter_context) { if (in_explicit_transaction) { @@ -2292,23 +2413,26 @@ PreparedQuery PrepareStorageModeQuery(ParsedQuery parsed_query, const bool in_ex auto *storage_mode_query = utils::Downcast<StorageModeQuery>(parsed_query.query); MG_ASSERT(storage_mode_query); - const auto storage_mode = ToStorageMode(storage_mode_query->storage_mode_); + const auto requested_mode = ToStorageMode(storage_mode_query->storage_mode_); + auto current_mode = interpreter_context->db->GetStorageMode(); - auto exists_active_transaction = interpreter_context->interpreters.WithLock([](const auto &interpreters_) { - return std::any_of(interpreters_.begin(), interpreters_.end(), [](const auto &interpreter) { - return interpreter->transaction_status_.load() != TransactionStatus::IDLE; - }); - }); - if (exists_active_transaction) { - spdlog::info( - "Storage mode will be modified when there are no other active transactions. Check the status of the " - "transactions using 'SHOW TRANSACTIONS' query and ensure no other transactions are active."); + std::function<void()> callback; + + if (current_mode == storage::StorageMode::ON_DISK_TRANSACTIONAL || + requested_mode == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + callback = SwitchMemoryDevice(current_mode, requested_mode, interpreter_context).fn; + } else { + if (ActiveTransactionsExist(interpreter_context)) { + spdlog::info( + "Storage mode will be modified when there are no other active transactions. Check the status of the " + "transactions using 'SHOW TRANSACTIONS' query and ensure no other transactions are active."); + } + + callback = [requested_mode, interpreter_context]() -> std::function<void()> { + return [interpreter_context, requested_mode] { interpreter_context->db->SetStorageMode(requested_mode); }; + }(); } - auto callback = [storage_mode, interpreter_context]() -> std::function<void()> { - return [interpreter_context, storage_mode] { interpreter_context->db->SetStorageMode(storage_mode); }; - }(); - return PreparedQuery{{}, std::move(parsed_query.required_privileges), [callback = std::move(callback)](AnyStream * /*stream*/, @@ -2325,20 +2449,25 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli throw CreateSnapshotInMulticommandTxException(); } + if (interpreter_context->db->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + throw CreateSnapshotDisabledOnDiskStorage(); + } + return PreparedQuery{ {}, std::move(parsed_query.required_privileges), [interpreter_context](AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { - if (auto maybe_error = interpreter_context->db->CreateSnapshot({}); maybe_error.HasError()) { + auto *mem_storage = static_cast<storage::InMemoryStorage *>(interpreter_context->db.get()); + if (auto maybe_error = mem_storage->CreateSnapshot({}); maybe_error.HasError()) { switch (maybe_error.GetError()) { - case storage::Storage::CreateSnapshotError::DisabledForReplica: + case storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica: throw utils::BasicException( "Failed to create a snapshot. Replica instances are not allowed to create them."); - case storage::Storage::CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: + case storage::InMemoryStorage::CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.", "https://memgr.ph/replication")); break; - case storage::Storage::CreateSnapshotError::ReachedMaxNumTries: + case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries: spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support"); break; } @@ -2586,7 +2715,7 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa case InfoQuery::InfoType::INDEX: header = {"index type", "label", "property"}; handler = [interpreter_context] { - auto *db = interpreter_context->db; + auto *db = interpreter_context->db.get(); auto info = db->ListAllIndices(); std::vector<std::vector<TypedValue>> results; results.reserve(info.label.size() + info.label_property.size()); @@ -2603,7 +2732,7 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa case InfoQuery::InfoType::CONSTRAINT: header = {"constraint type", "label", "properties"}; handler = [interpreter_context] { - auto *db = interpreter_context->db; + auto *db = interpreter_context->db.get(); auto info = db->ListAllConstraints(); std::vector<std::vector<TypedValue>> results; results.reserve(info.existence.size() + info.unique.size()); @@ -2690,7 +2819,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name, properties_stringified = std::move(properties_stringified), properties = std::move(properties)](Notification &constraint_notification) { - auto maybe_constraint_error = interpreter_context->db->CreateExistenceConstraint(label, properties[0]); + auto maybe_constraint_error = interpreter_context->db->CreateExistenceConstraint(label, properties[0], {}); if (maybe_constraint_error.HasError()) { const auto &error = maybe_constraint_error.GetError(); @@ -2713,9 +2842,12 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ properties_stringified); } else if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) { throw ReplicationException( - "At least one SYNC replica has not confirmed the creation of the EXISTS constraint on label " + "At least one SYNC replica has not confirmed the creation of the EXISTS constraint on " + "label " "{} on properties {}.", label_name, properties_stringified); + } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintsPersistenceError>) { + throw ConstraintsPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -2738,11 +2870,12 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name, properties_stringified = std::move(properties_stringified), property_set = std::move(property_set)](Notification &constraint_notification) { - auto maybe_constraint_error = interpreter_context->db->CreateUniqueConstraint(label, property_set); + auto maybe_constraint_error = interpreter_context->db->CreateUniqueConstraint(label, property_set, {}); if (maybe_constraint_error.HasError()) { const auto &error = maybe_constraint_error.GetError(); std::visit( - [&interpreter_context, &label_name, &properties_stringified]<typename T>(T &&arg) { + [&interpreter_context, &label_name, &properties_stringified, + &constraint_notification]<typename T>(T &&arg) { using ErrorType = std::remove_cvref_t<T>; if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) { auto &violation = arg; @@ -2756,10 +2889,18 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ "Unable to create unique constraint :{}({}), because an " "existing node violates it.", violation_label_name, property_names_stream.str()); + } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintDefinitionError>) { + constraint_notification.code = NotificationCode::EXISTENT_CONSTRAINT; + constraint_notification.title = + fmt::format("Constraint UNIQUE on label {} and properties {} couldn't be created.", + label_name, properties_stringified); } else if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) { - throw ReplicationException(fmt::format( - "At least one SYNC replica has not confirmed the creation of the UNIQUE constraint: {}({}).", - label_name, properties_stringified)); + throw ReplicationException( + fmt::format("At least one SYNC replica has not confirmed the creation of the UNIQUE " + "constraint: {}({}).", + label_name, properties_stringified)); + } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintsPersistenceError>) { + throw ConstraintsPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -2805,7 +2946,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name, properties_stringified = std::move(properties_stringified), properties = std::move(properties)](Notification &constraint_notification) { - auto maybe_constraint_error = interpreter_context->db->DropExistenceConstraint(label, properties[0]); + auto maybe_constraint_error = interpreter_context->db->DropExistenceConstraint(label, properties[0], {}); if (maybe_constraint_error.HasError()) { const auto &error = maybe_constraint_error.GetError(); std::visit( @@ -2821,6 +2962,8 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ fmt::format("At least one SYNC replica has not confirmed the dropping of the EXISTS " "constraint on label {} on properties {}.", label_name, properties_stringified)); + } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintsPersistenceError>) { + throw ConstraintsPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -2844,7 +2987,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name, properties_stringified = std::move(properties_stringified), property_set = std::move(property_set)](Notification &constraint_notification) { - auto maybe_constraint_error = interpreter_context->db->DropUniqueConstraint(label, property_set); + auto maybe_constraint_error = interpreter_context->db->DropUniqueConstraint(label, property_set, {}); if (maybe_constraint_error.HasError()) { const auto &error = maybe_constraint_error.GetError(); std::visit( @@ -2855,6 +2998,8 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ fmt::format("At least one SYNC replica has not confirmed the dropping of the UNIQUE " "constraint on label {} on properties {}.", label_name, properties_stringified)); + } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintsPersistenceError>) { + throw ConstraintsPersistenceException(); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -3021,8 +3166,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, utils::Downcast<TriggerQuery>(parsed_query.query) || utils::Downcast<AnalyzeGraphQuery>(parsed_query.query) || utils::Downcast<TransactionQueueQuery>(parsed_query.query))) { memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveTransactions); - db_accessor_ = - std::make_unique<storage::Storage::Accessor>(interpreter_context_->db->Access(GetIsolationLevelOverride())); + db_accessor_ = interpreter_context_->db->Access(GetIsolationLevelOverride()); execution_db_accessor_.emplace(db_accessor_.get()); transaction_status_.store(TransactionStatus::ACTIVE, std::memory_order_release); @@ -3066,7 +3210,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, &*execution_db_accessor_, &query_execution->execution_memory_with_exception, username, &transaction_status_); } else if (utils::Downcast<InfoQuery>(parsed_query.query)) { prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary, - interpreter_context_, interpreter_context_->db, + interpreter_context_, interpreter_context_->db.get(), &query_execution->execution_memory_with_exception, interpreter_isolation_level, next_transaction_isolation_level); } else if (utils::Downcast<ConstraintQuery>(parsed_query.query)) { @@ -3077,8 +3221,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, interpreter_context_, &*execution_db_accessor_); } else if (utils::Downcast<LockPathQuery>(parsed_query.query)) { - prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_, - &*execution_db_accessor_); + prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_); } else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) { prepared_query = PrepareFreeMemoryQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_); } else if (utils::Downcast<ShowConfigQuery>(parsed_query.query)) { @@ -3118,9 +3261,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, UpdateTypeCount(rw_type); - if (const auto query_type = query_execution->prepared_query->rw_type; - interpreter_context_->db->GetReplicationRole() == storage::replication::ReplicationRole::REPLICA && - (query_type == RWType::W || query_type == RWType::RW)) { + if (IsWriteQueryOnMainMemoryReplica(interpreter_context_->db.get(), rw_type)) { query_execution = nullptr; throw QueryException("Write query forbidden on the replica!"); } @@ -3165,6 +3306,9 @@ void Interpreter::Abort() { if (!db_accessor_) return; db_accessor_->Abort(); + for (auto &qe : query_executions_) { + if (qe) qe->CleanRuntimeData(); + } execution_db_accessor_.reset(); db_accessor_.reset(); trigger_context_collector_.reset(); @@ -3180,7 +3324,7 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret // create a new transaction for each trigger auto storage_acc = interpreter_context->db->Access(); - DbAccessor db_accessor{&storage_acc}; + DbAccessor db_accessor{storage_acc.get()}; trigger_context.AdaptForAccessor(&db_accessor); try { @@ -3223,6 +3367,8 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret trigger.Name(), label_name, property_names_stream.str()); } } + } else if constexpr (std::is_same_v<ErrorType, storage::SerializationError>) { + throw QueryException("Unable to commit due to serialization error."); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -3260,6 +3406,14 @@ void Interpreter::Commit() { utils::OnScopeExit clean_status( [this]() { transaction_status_.store(TransactionStatus::IDLE, std::memory_order_release); }); + auto current_storage_mode = interpreter_context_->db->GetStorageMode(); + auto creation_mode = db_accessor_->GetCreationStorageMode(); + if (creation_mode != storage::StorageMode::ON_DISK_TRANSACTIONAL && + current_storage_mode == storage::StorageMode::ON_DISK_TRANSACTIONAL) { + throw QueryException( + "Cannot commit transaction because the storage mode has changed from in-memory storage to on-disk storage."); + } + utils::OnScopeExit update_metrics([]() { memgraph::metrics::IncrementCounter(memgraph::metrics::CommitedTransactions); memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveTransactions); @@ -3293,6 +3447,9 @@ void Interpreter::Commit() { } const auto reset_necessary_members = [this]() { + for (auto &qe : query_executions_) { + if (qe) qe->CleanRuntimeData(); + } execution_db_accessor_.reset(); db_accessor_.reset(); trigger_context_collector_.reset(); @@ -3331,6 +3488,8 @@ void Interpreter::Commit() { property_names_stream.str()); } } + } else if constexpr (std::is_same_v<ErrorType, storage::SerializationError>) { + throw QueryException("Unable to commit due to serialization error."); } else { static_assert(kAlwaysFalse<T>, "Missing type from variant visitor"); } @@ -3338,11 +3497,11 @@ void Interpreter::Commit() { error); } - // The ordered execution of after commit triggers is heavily depending on the exclusiveness of db_accessor_->Commit(): - // only one of the transactions can be commiting at the same time, so when the commit is finished, that transaction - // probably will schedule its after commit triggers, because the other transactions that want to commit are still - // waiting for commiting or one of them just started commiting its changes. - // This means the ordered execution of after commit triggers are not guaranteed. + // The ordered execution of after commit triggers is heavily depending on the exclusiveness of + // db_accessor_->Commit(): only one of the transactions can be commiting at the same time, so when the commit is + // finished, that transaction probably will schedule its after commit triggers, because the other transactions that + // want to commit are still waiting for commiting or one of them just started commiting its changes. This means the + // ordered execution of after commit triggers are not guaranteed. if (trigger_context && interpreter_context_->trigger_store.AfterCommitTriggers().size() > 0) { interpreter_context_->after_commit_trigger_pool.AddTask( [this, trigger_context = std::move(*trigger_context), diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index a6b7052e2..f294fce3b 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -32,7 +32,10 @@ #include "query/stream/streams.hpp" #include "query/trigger.hpp" #include "query/typed_value.hpp" +#include "spdlog/spdlog.h" +#include "storage/v2/disk/storage.hpp" #include "storage/v2/isolation_level.hpp" +#include "storage/v2/storage.hpp" #include "utils/event_counter.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" @@ -208,11 +211,15 @@ class Interpreter; * running concurrently). * */ +/// TODO: andi decouple in a separate file why here? struct InterpreterContext { - explicit InterpreterContext(storage::Storage *db, InterpreterConfig config, + explicit InterpreterContext(storage::Config storage_config, InterpreterConfig interpreter_config, const std::filesystem::path &data_directory); - storage::Storage *db; + InterpreterContext(std::unique_ptr<storage::Storage> db, InterpreterConfig interpreter_config, + const std::filesystem::path &data_directory); + + std::unique_ptr<storage::Storage> db; // ANTLR has singleton instance that is shared between threads. It is // protected by locks inside of ANTLR. Unfortunately, they are not protected @@ -319,9 +326,6 @@ class Interpreter final { void BeginTransaction(const std::map<std::string, storage::PropertyValue> &metadata = {}); - /* - Returns transaction id or empty if the db_accessor is not initialized. - */ std::optional<uint64_t> GetTransactionId() const; void CommitTransaction(); @@ -378,6 +382,13 @@ class Interpreter final { prepared_query.reset(); std::visit([](auto &memory_resource) { memory_resource.Release(); }, execution_memory); } + + void CleanRuntimeData() { + if (prepared_query.has_value()) { + prepared_query.reset(); + } + notifications.clear(); + } }; // Interpreter supports multiple prepared queries at the same time. @@ -533,4 +544,5 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std: // don't return the execution summary as it's not finished return {{"has_more", TypedValue(true)}}; } + } // namespace memgraph::query diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 040f2da95..91ebad094 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -54,6 +54,7 @@ #include "utils/likely.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" +#include "utils/message.hpp" #include "utils/pmr/deque.hpp" #include "utils/pmr/list.hpp" #include "utils/pmr/unordered_map.hpp" @@ -813,10 +814,12 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, ExecutionContext &context) { // old_node_value may be Null when using optional matching if (!existing_node.IsNull()) { ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex); + context.db_accessor->PrefetchInEdges(vertex); in_edges_.emplace( UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex()))); } } else { + context.db_accessor->PrefetchInEdges(vertex); in_edges_.emplace(UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types))); } if (in_edges_) { @@ -830,10 +833,12 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, ExecutionContext &context) { // old_node_value may be Null when using optional matching if (!existing_node.IsNull()) { ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex); + context.db_accessor->PrefetchOutEdges(vertex); out_edges_.emplace( UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex()))); } } else { + context.db_accessor->PrefetchOutEdges(vertex); out_edges_.emplace(UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types))); } if (out_edges_) { @@ -891,7 +896,8 @@ namespace { * @return See above. */ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction direction, - const std::vector<storage::EdgeTypeId> &edge_types, utils::MemoryResource *memory) { + const std::vector<storage::EdgeTypeId> &edge_types, utils::MemoryResource *memory, + DbAccessor *db_accessor) { // wraps an EdgeAccessor into a pair <accessor, direction> auto wrapper = [](EdgeAtom::Direction direction, auto &&edges) { return iter::imap([direction](const auto &edge) { return std::make_pair(edge, direction); }, @@ -902,6 +908,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio utils::pmr::vector<decltype(wrapper(direction, *vertex.InEdges(view, edge_types)))> chain_elements(memory); if (direction != EdgeAtom::Direction::OUT) { + db_accessor->PrefetchInEdges(vertex); auto edges = UnwrapEdgesResult(vertex.InEdges(view, edge_types)); if (edges.begin() != edges.end()) { chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges))); @@ -909,6 +916,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio } if (direction != EdgeAtom::Direction::IN) { + db_accessor->PrefetchOutEdges(vertex); auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types)); if (edges.begin() != edges.end()) { chain_elements.emplace_back(wrapper(EdgeAtom::Direction::OUT, std::move(edges))); @@ -974,8 +982,9 @@ class ExpandVariableCursor : public Cursor { // a stack of edge iterables corresponding to the level/depth of // the expansion currently being Pulled - using ExpandEdges = decltype(ExpandFromVertex(std::declval<VertexAccessor>(), EdgeAtom::Direction::IN, - self_.common_.edge_types, utils::NewDeleteResource())); + using ExpandEdges = + decltype(ExpandFromVertex(std::declval<VertexAccessor>(), EdgeAtom::Direction::IN, self_.common_.edge_types, + utils::NewDeleteResource(), std::declval<DbAccessor *>())); utils::pmr::vector<ExpandEdges> edges_; // an iterator indicating the position in the corresponding edges_ element @@ -1016,7 +1025,8 @@ class ExpandVariableCursor : public Cursor { if (upper_bound_ > 0) { auto *memory = edges_.get_allocator().GetMemoryResource(); - edges_.emplace_back(ExpandFromVertex(vertex, self_.common_.direction, self_.common_.edge_types, memory)); + edges_.emplace_back( + ExpandFromVertex(vertex, self_.common_.direction, self_.common_.edge_types, memory, context.db_accessor)); edges_it_.emplace_back(edges_.back().begin()); } @@ -1122,8 +1132,8 @@ class ExpandVariableCursor : public Cursor { // edge's expansions onto the stack, if we should continue to expand if (upper_bound_ > static_cast<int64_t>(edges_.size())) { auto *memory = edges_.get_allocator().GetMemoryResource(); - edges_.emplace_back( - ExpandFromVertex(current_vertex, self_.common_.direction, self_.common_.edge_types, memory)); + edges_.emplace_back(ExpandFromVertex(current_vertex, self_.common_.direction, self_.common_.edge_types, memory, + context.db_accessor)); edges_it_.emplace_back(edges_.back().begin()); } @@ -1266,6 +1276,7 @@ class STShortestPathCursor : public query::plan::Cursor { for (const auto &vertex : source_frontier) { if (self_.common_.direction != EdgeAtom::Direction::IN) { + context.db_accessor->PrefetchOutEdges(vertex); auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) { #ifdef MG_ENTERPRISE @@ -1292,6 +1303,7 @@ class STShortestPathCursor : public query::plan::Cursor { } } if (self_.common_.direction != EdgeAtom::Direction::OUT) { + dba.PrefetchInEdges(vertex); auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) { #ifdef MG_ENTERPRISE @@ -1332,6 +1344,7 @@ class STShortestPathCursor : public query::plan::Cursor { // reversed. for (const auto &vertex : sink_frontier) { if (self_.common_.direction != EdgeAtom::Direction::OUT) { + context.db_accessor->PrefetchOutEdges(vertex); auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) { #ifdef MG_ENTERPRISE @@ -1357,6 +1370,7 @@ class STShortestPathCursor : public query::plan::Cursor { } } if (self_.common_.direction != EdgeAtom::Direction::IN) { + dba.PrefetchInEdges(vertex); auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) { #ifdef MG_ENTERPRISE @@ -1446,12 +1460,14 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor { // populates the to_visit_next_ structure with expansions // from the given vertex. skips expansions that don't satisfy // the "where" condition. - auto expand_from_vertex = [this, &expand_pair](const auto &vertex) { + auto expand_from_vertex = [this, &expand_pair, &context](const auto &vertex) { if (self_.common_.direction != EdgeAtom::Direction::IN) { + context.db_accessor->PrefetchOutEdges(vertex); auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) expand_pair(edge, edge.To()); } if (self_.common_.direction != EdgeAtom::Direction::OUT) { + context.db_accessor->PrefetchInEdges(vertex); auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) expand_pair(edge, edge.From()); } @@ -1646,15 +1662,17 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor { // Populates the priority queue structure with expansions // from the given vertex. skips expansions that don't satisfy // the "where" condition. - auto expand_from_vertex = [this, &expand_pair](const VertexAccessor &vertex, const TypedValue &weight, - int64_t depth) { + auto expand_from_vertex = [this, &expand_pair, &context](const VertexAccessor &vertex, const TypedValue &weight, + int64_t depth) { if (self_.common_.direction != EdgeAtom::Direction::IN) { + context.db_accessor->PrefetchOutEdges(vertex); auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) { expand_pair(edge, edge.To(), weight, depth); } } if (self_.common_.direction != EdgeAtom::Direction::OUT) { + context.db_accessor->PrefetchInEdges(vertex); auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) { expand_pair(edge, edge.From(), weight, depth); @@ -1913,6 +1931,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor { auto expand_from_vertex = [this, &expand_vertex, &context](const VertexAccessor &vertex, const TypedValue &weight, int64_t depth) { if (self_.common_.direction != EdgeAtom::Direction::IN) { + context.db_accessor->PrefetchOutEdges(vertex); auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) { #ifdef MG_ENTERPRISE @@ -1927,6 +1946,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor { } } if (self_.common_.direction != EdgeAtom::Direction::OUT) { + context.db_accessor->PrefetchInEdges(vertex); auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) { #ifdef MG_ENTERPRISE diff --git a/src/query/plan/operator.hpp b/src/query/plan/operator.hpp index 3d95d1da0..79df74ffb 100644 --- a/src/query/plan/operator.hpp +++ b/src/query/plan/operator.hpp @@ -28,6 +28,7 @@ #include "utils/fnv.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" +#include "utils/synchronized.hpp" #include "utils/visitor.hpp" namespace memgraph { diff --git a/src/query/plan/rewrite/index_lookup.hpp b/src/query/plan/rewrite/index_lookup.hpp index 9e4fc7c9a..2ed261ccb 100644 --- a/src/query/plan/rewrite/index_lookup.hpp +++ b/src/query/plan/rewrite/index_lookup.hpp @@ -27,7 +27,6 @@ #include "query/plan/operator.hpp" #include "query/plan/preprocess.hpp" -#include "storage/v2/indices.hpp" DECLARE_int64(query_vertex_count_to_expand_existing); diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index bc4dbffcb..9309b2b4b 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -21,10 +21,12 @@ #include <stdexcept> #include <type_traits> #include <utility> +#include <variant> #include "license/license.hpp" #include "mg_procedure.h" #include "module.hpp" +#include "query/db_accessor.hpp" #include "query/frontend/ast/ast.hpp" #include "query/procedure/cypher_types.hpp" #include "query/procedure/mg_procedure_helpers.hpp" @@ -1950,9 +1952,9 @@ void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) { const auto *auth_checker = it.source_vertex.graph->ctx->auth_checker.get(); const auto view = it.source_vertex.graph->view; while (*impl_it != end) { - if (auth_checker->Has(**impl_it, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) { - const auto &check_vertex = - it.source_vertex.getImpl() == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From(); + auto edgeAcc = **impl_it; + if (auth_checker->Has(edgeAcc, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) { + const auto &check_vertex = it.source_vertex.getImpl() == edgeAcc.From() ? edgeAcc.To() : edgeAcc.From(); if (auth_checker->Has(check_vertex, view, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) { break; } @@ -1968,6 +1970,14 @@ void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) { mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) { return WrapExceptions( [v, memory] { + auto dbAccessor = v->graph->impl; + if (std::holds_alternative<memgraph::query::DbAccessor *>(dbAccessor)) { + std::get<memgraph::query::DbAccessor *>(dbAccessor) + ->PrefetchInEdges(std::get<memgraph::query::VertexAccessor>(v->impl)); + } else { + std::get<memgraph::query::SubgraphDbAccessor *>(dbAccessor) + ->PrefetchInEdges(std::get<memgraph::query::SubgraphVertexAccessor>(v->impl)); + } auto it = NewMgpObject<mgp_edges_iterator>(memory, *v); MG_ASSERT(it != nullptr); @@ -1995,19 +2005,20 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_ #endif if (*it->in_it != it->in->end()) { - std::visit(memgraph::utils::Overloaded{ - [&](memgraph::query::DbAccessor *) { - it->current_e.emplace(**it->in_it, (**it->in_it).From(), (**it->in_it).To(), v->graph, - it->GetMemoryResource()); - }, - [&](memgraph::query::SubgraphDbAccessor *impl) { - it->current_e.emplace( - **it->in_it, - memgraph::query::SubgraphVertexAccessor((**it->in_it).From(), impl->getGraph()), - memgraph::query::SubgraphVertexAccessor((**it->in_it).To(), impl->getGraph()), v->graph, - it->GetMemoryResource()); - }}, - v->graph->impl); + std::visit( + memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { + auto edgeAcc = **it->in_it; + it->current_e.emplace(edgeAcc, edgeAcc.From(), edgeAcc.To(), v->graph, it->GetMemoryResource()); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + auto edgeAcc = **it->in_it; + it->current_e.emplace(edgeAcc, + memgraph::query::SubgraphVertexAccessor(edgeAcc.From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(edgeAcc.To(), impl->getGraph()), + v->graph, it->GetMemoryResource()); + }}, + v->graph->impl); } return it.release(); @@ -2018,6 +2029,14 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) { return WrapExceptions( [v, memory] { + auto dbAccessor = v->graph->impl; + if (std::holds_alternative<memgraph::query::DbAccessor *>(dbAccessor)) { + std::get<memgraph::query::DbAccessor *>(dbAccessor) + ->PrefetchOutEdges(std::get<memgraph::query::VertexAccessor>(v->impl)); + } else { + std::get<memgraph::query::SubgraphDbAccessor *>(dbAccessor) + ->PrefetchOutEdges(std::get<memgraph::query::SubgraphVertexAccessor>(v->impl)); + } auto it = NewMgpObject<mgp_edges_iterator>(memory, *v); MG_ASSERT(it != nullptr); auto maybe_edges = std::visit([v](auto &impl) { return impl.OutEdges(v->graph->view); }, v->impl); @@ -2047,19 +2066,20 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges #endif if (*it->out_it != it->out->end()) { - std::visit(memgraph::utils::Overloaded{ - [&](memgraph::query::DbAccessor *) { - it->current_e.emplace(**it->out_it, (**it->out_it).From(), (**it->out_it).To(), v->graph, - it->GetMemoryResource()); - }, - [&](memgraph::query::SubgraphDbAccessor *impl) { - it->current_e.emplace( - **it->out_it, - memgraph::query::SubgraphVertexAccessor((**it->out_it).From(), impl->getGraph()), - memgraph::query::SubgraphVertexAccessor((**it->out_it).To(), impl->getGraph()), v->graph, - it->GetMemoryResource()); - }}, - v->graph->impl); + std::visit( + memgraph::utils::Overloaded{ + [&](memgraph::query::DbAccessor *) { + memgraph::query::EdgeAccessor edgeAcc = **it->out_it; + it->current_e.emplace(edgeAcc, edgeAcc.From(), edgeAcc.To(), v->graph, it->GetMemoryResource()); + }, + [&](memgraph::query::SubgraphDbAccessor *impl) { + auto edgeAcc = **it->out_it; + it->current_e.emplace(edgeAcc, + memgraph::query::SubgraphVertexAccessor(edgeAcc.From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(edgeAcc.To(), impl->getGraph()), + v->graph, it->GetMemoryResource()); + }}, + v->graph->impl); } return it.release(); @@ -2110,13 +2130,15 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) { } std::visit(memgraph::utils::Overloaded{ [&](memgraph::query::DbAccessor *) { - it->current_e.emplace(**impl_it, (**impl_it).From(), (**impl_it).To(), - it->source_vertex.graph, it->GetMemoryResource()); + auto edgeAcc = **impl_it; + it->current_e.emplace(edgeAcc, edgeAcc.From(), edgeAcc.To(), it->source_vertex.graph, + it->GetMemoryResource()); }, [&](memgraph::query::SubgraphDbAccessor *impl) { + auto edgeAcc = **impl_it; it->current_e.emplace( - **impl_it, memgraph::query::SubgraphVertexAccessor((**impl_it).From(), impl->getGraph()), - memgraph::query::SubgraphVertexAccessor((**impl_it).To(), impl->getGraph()), + edgeAcc, memgraph::query::SubgraphVertexAccessor(edgeAcc.From(), impl->getGraph()), + memgraph::query::SubgraphVertexAccessor(edgeAcc.To(), impl->getGraph()), it->source_vertex.graph, it->GetMemoryResource()); }}, it->source_vertex.graph->impl); diff --git a/src/query/procedure/mg_procedure_impl.hpp b/src/query/procedure/mg_procedure_impl.hpp index 209ea3aa9..a015103c7 100644 --- a/src/query/procedure/mg_procedure_impl.hpp +++ b/src/query/procedure/mg_procedure_impl.hpp @@ -435,28 +435,24 @@ struct mgp_vertex { /// the allocator which was used to allocate `this`. using allocator_type = memgraph::utils::Allocator<mgp_vertex>; - // Hopefully VertexAccessor copy constructor remains noexcept, so that we can - // have everything noexcept here. - static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::VertexAccessor>); - - mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept + mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) : memory(memory), impl(v), graph(graph) {} - mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph, - memgraph::utils::MemoryResource *memory) noexcept + mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) : memory(memory), impl(v), graph(graph) {} - mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept + mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) : memory(memory), impl(other.impl), graph(other.graph) {} - mgp_vertex(mgp_vertex &&other, memgraph::utils::MemoryResource *memory) noexcept + mgp_vertex(mgp_vertex &&other, memgraph::utils::MemoryResource *memory) : memory(memory), impl(other.impl), graph(other.graph) {} - mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {} + // NOLINTNEXTLINE(hicpp-noexcept-move, performance-noexcept-move-constructor) + mgp_vertex(mgp_vertex &&other) : memory(other.memory), impl(other.impl), graph(other.graph) {} memgraph::query::VertexAccessor getImpl() const { return std::visit( - memgraph::utils::Overloaded{[](memgraph::query::VertexAccessor impl) { return impl; }, + memgraph::utils::Overloaded{[](const memgraph::query::VertexAccessor &impl) { return impl; }, [](memgraph::query::SubgraphVertexAccessor impl) { return impl.impl_; }}, this->impl); } @@ -486,33 +482,28 @@ struct mgp_edge { /// the allocator which was used to allocate `this`. using allocator_type = memgraph::utils::Allocator<mgp_edge>; - // Hopefully EdgeAccessor copy constructor remains noexcept, so that we can - // have everything noexcept here. - static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::EdgeAccessor>); - static mgp_edge *Copy(const mgp_edge &edge, mgp_memory &memory); - mgp_edge(const memgraph::query::EdgeAccessor &impl, mgp_graph *graph, - memgraph::utils::MemoryResource *memory) noexcept + mgp_edge(const memgraph::query::EdgeAccessor &impl, mgp_graph *graph, memgraph::utils::MemoryResource *memory) : memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {} mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::VertexAccessor &from_v, - const memgraph::query::VertexAccessor &to_v, mgp_graph *graph, - memgraph::utils::MemoryResource *memory) noexcept + const memgraph::query::VertexAccessor &to_v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) : memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {} mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::SubgraphVertexAccessor &from_v, const memgraph::query::SubgraphVertexAccessor &to_v, mgp_graph *graph, - memgraph::utils::MemoryResource *memory) noexcept + memgraph::utils::MemoryResource *memory) : memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {} - mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept + mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) : memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {} - mgp_edge(mgp_edge &&other, memgraph::utils::MemoryResource *memory) noexcept + mgp_edge(mgp_edge &&other, memgraph::utils::MemoryResource *memory) : memory(other.memory), impl(other.impl), from(std::move(other.from), memory), to(std::move(other.to), memory) {} - mgp_edge(mgp_edge &&other) noexcept + // NOLINTNEXTLINE(hicpp-noexcept-move, performance-noexcept-move-constructor) + mgp_edge(mgp_edge &&other) : memory(other.memory), impl(other.impl), from(std::move(other.from)), to(std::move(other.to)) {} /// Copy construction without memgraph::utils::MemoryResource is not allowed. @@ -671,14 +662,12 @@ struct mgp_properties_iterator { struct mgp_edges_iterator { using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>; - // Hopefully mgp_vertex copy constructor remains noexcept, so that we can - // have everything noexcept here. - static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>); - mgp_edges_iterator(const mgp_vertex &v, memgraph::utils::MemoryResource *memory) noexcept + mgp_edges_iterator(const mgp_vertex &v, memgraph::utils::MemoryResource *memory) : memory(memory), source_vertex(v, memory) {} - mgp_edges_iterator(mgp_edges_iterator &&other) noexcept + // NOLINTNEXTLINE(hicpp-noexcept-move, performance-noexcept-move-constructor) + mgp_edges_iterator(mgp_edges_iterator &&other) : memory(other.memory), source_vertex(std::move(other.source_vertex)), in(std::move(other.in)), diff --git a/src/query/stream/streams.cpp b/src/query/stream/streams.cpp index e3666c510..8e4620d6a 100644 --- a/src/query/stream/streams.cpp +++ b/src/query/stream/streams.cpp @@ -496,7 +496,7 @@ Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std [interpreter_context, interpreter]() { interpreter_context->interpreters->erase(interpreter.get()); }}; memgraph::metrics::IncrementCounter(memgraph::metrics::MessagesConsumed, messages.size()); - CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name); + CallCustomTransformation(transformation_name, messages, result, *accessor, *memory_resource, stream_name); DiscardValueResultStream stream; @@ -743,7 +743,7 @@ TransformationResult Streams::Check(const std::string &stream_name, std::optiona &transformation_name = transformation_name, &result, &test_result]<typename T>(const std::vector<T> &messages) mutable { auto accessor = interpreter_context->db->Access(); - CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name); + CallCustomTransformation(transformation_name, messages, result, *accessor, *memory_resource, stream_name); auto result_row = std::vector<TypedValue>(); result_row.reserve(kCheckStreamResultSize); diff --git a/src/query/trigger_context.cpp b/src/query/trigger_context.cpp index 2b1dc7079..2f30974e4 100644 --- a/src/query/trigger_context.cpp +++ b/src/query/trigger_context.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -302,6 +302,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { if (!maybe_from_vertex) { continue; } + accessor->PrefetchOutEdges(*maybe_from_vertex); auto maybe_out_edges = maybe_from_vertex->OutEdges(storage::View::OLD); MG_ASSERT(maybe_out_edges.HasValue()); const auto edge_gid = created_edge.object.Gid(); @@ -323,6 +324,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { auto it = values->begin(); for (const auto &value : *values) { if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::View::OLD); maybe_vertex) { + accessor->PrefetchOutEdges(*maybe_vertex); auto maybe_out_edges = maybe_vertex->OutEdges(storage::View::OLD); MG_ASSERT(maybe_out_edges.HasValue()); for (const auto &edge : *maybe_out_edges) { diff --git a/src/query/trigger_context.hpp b/src/query/trigger_context.hpp index 39f4a89dd..abb1fd6b0 100644 --- a/src/query/trigger_context.hpp +++ b/src/query/trigger_context.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -160,11 +160,6 @@ enum class TriggerEventType : uint8_t { const char *TriggerEventTypeToString(TriggerEventType event_type); -static_assert(std::is_trivially_copy_constructible_v<VertexAccessor>, - "VertexAccessor is not trivially copy constructible, move it where possible and remove this assert"); -static_assert(std::is_trivially_copy_constructible_v<EdgeAccessor>, - "EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert"); - // Holds the information necessary for triggers class TriggerContext { public: diff --git a/src/query/typed_value.hpp b/src/query/typed_value.hpp index bebda5ce4..d5a38f9a1 100644 --- a/src/query/typed_value.hpp +++ b/src/query/typed_value.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/storage/README.md b/src/storage/README.md new file mode 100644 index 000000000..61d5512d8 --- /dev/null +++ b/src/storage/README.md @@ -0,0 +1,3 @@ +# Storage Modes + +* `ON_DISK_TRANSACTIONAL` diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index 984f63a65..099cf2ae1 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -1,16 +1,27 @@ set(storage_v2_src_files commit_log.cpp - constraints.cpp + constraints/existence_constraints.cpp temporal.cpp durability/durability.cpp durability/serialization.cpp durability/snapshot.cpp durability/wal.cpp edge_accessor.cpp - indices.cpp property_store.cpp vertex_accessor.cpp storage.cpp + indices/indices.cpp + all_vertices_iterable.cpp + vertices_iterable.cpp + inmemory/storage.cpp + inmemory/label_index.cpp + inmemory/label_property_index.cpp + inmemory/unique_constraints.cpp + disk/storage.cpp + disk/rocksdb_storage.cpp + disk/label_index.cpp + disk/label_property_index.cpp + disk/unique_constraints.cpp storage_mode.cpp isolation_level.cpp) diff --git a/src/storage/v2/all_vertices_iterable.cpp b/src/storage/v2/all_vertices_iterable.cpp new file mode 100644 index 000000000..46fb8e521 --- /dev/null +++ b/src/storage/v2/all_vertices_iterable.cpp @@ -0,0 +1,44 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/all_vertices_iterable.hpp" + +namespace memgraph::storage { + +auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end, + std::optional<VertexAccessor> *vertex, Transaction *tx, View view, Indices *indices, + Constraints *constraints, Config::Items config) { + while (it != end) { + *vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view); + if (!*vertex) { + ++it; + continue; + } + break; + } + return it; +} + +AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it) + : self_(self), + it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, + self->indices_, self_->constraints_, self->config_)) {} + +VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } + +AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() { + ++it_; + it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_, + self_->indices_, self_->constraints_, self_->config_); + return *this; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/all_vertices_iterable.hpp b/src/storage/v2/all_vertices_iterable.hpp new file mode 100644 index 000000000..19b12d50b --- /dev/null +++ b/src/storage/v2/all_vertices_iterable.hpp @@ -0,0 +1,58 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/vertex_accessor.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::storage { + +class AllVerticesIterable final { + utils::SkipList<Vertex>::Accessor vertices_accessor_; + Transaction *transaction_; + View view_; + Indices *indices_; + Constraints *constraints_; + Config::Items config_; + std::optional<VertexAccessor> vertex_; + + public: + class Iterator final { + AllVerticesIterable *self_; + utils::SkipList<Vertex>::Iterator it_; + + public: + Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it); + + VertexAccessor operator*() const; + + Iterator &operator++(); + + bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; } + + bool operator!=(const Iterator &other) const { return !(*this == other); } + }; + + AllVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view, + Indices *indices, Constraints *constraints, Config::Items config) + : vertices_accessor_(std::move(vertices_accessor)), + transaction_(transaction), + view_(view), + indices_(indices), + constraints_(constraints), + config_(config) {} + + Iterator begin() { return {this, vertices_accessor_.begin()}; } + Iterator end() { return {this, vertices_accessor_.end()}; } +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/config.hpp b/src/storage/v2/config.hpp index 3b1beec92..04ce5882d 100644 --- a/src/storage/v2/config.hpp +++ b/src/storage/v2/config.hpp @@ -60,6 +60,17 @@ struct Config { struct Transaction { IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION}; } transaction; + + struct DiskConfig { + std::filesystem::path main_storage_directory{"rocksdb_main_storage"}; + std::filesystem::path label_index_directory{"rocksdb_label_index"}; + std::filesystem::path label_property_index_directory{"rocksdb_label_property_index"}; + std::filesystem::path unique_constraints_directory{"rocksdb_unique_constraints"}; + std::filesystem::path name_id_mapper_directory{"rocksdb_name_id_mapper"}; + std::filesystem::path id_name_mapper_directory{"rocksdb_id_name_mapper"}; + std::filesystem::path durability_directory{"rocksdb_durability"}; + std::filesystem::path wal_directory{"rocksdb_wal"}; + } disk; }; } // namespace memgraph::storage diff --git a/src/storage/v2/constraints.hpp b/src/storage/v2/constraints.hpp deleted file mode 100644 index b209437f8..000000000 --- a/src/storage/v2/constraints.hpp +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2022 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#pragma once - -#include <optional> -#include <set> -#include <vector> - -#include "storage/v2/id_types.hpp" -#include "storage/v2/transaction.hpp" -#include "storage/v2/vertex.hpp" -#include "utils/logging.hpp" -#include "utils/result.hpp" -#include "utils/skip_list.hpp" - -namespace memgraph::storage { - -// NOLINTNEXTLINE(misc-definitions-in-headers) -const size_t kUniqueConstraintsMaxProperties = 32; - -/// Utility class to store data in a fixed size array. The array is used -/// instead of `std::vector` to avoid `std::bad_alloc` exception where not -/// necessary. -template <class T> -struct FixedCapacityArray { - size_t size; - T values[kUniqueConstraintsMaxProperties]; - - explicit FixedCapacityArray(size_t array_size) : size(array_size) { - MG_ASSERT(size <= kUniqueConstraintsMaxProperties, "Invalid array size!"); - } -}; - -using PropertyIdArray = FixedCapacityArray<PropertyId>; - -struct ConstraintViolation { - enum class Type { - EXISTENCE, - UNIQUE, - }; - - Type type; - LabelId label; - - // While multiple properties are supported by unique constraints, the - // `properties` set will always have exactly one element in the case of - // existence constraint violation. - std::set<PropertyId> properties; -}; - -bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs); - -class UniqueConstraints { - private: - struct Entry { - std::vector<PropertyValue> values; - const Vertex *vertex; - uint64_t timestamp; - - bool operator<(const Entry &rhs); - bool operator==(const Entry &rhs); - - bool operator<(const std::vector<PropertyValue> &rhs); - bool operator==(const std::vector<PropertyValue> &rhs); - }; - - public: - /// Status for creation of unique constraints. - /// Note that this does not cover the case when the constraint is violated. - enum class CreationStatus { - SUCCESS, - ALREADY_EXISTS, - EMPTY_PROPERTIES, - PROPERTIES_SIZE_LIMIT_EXCEEDED, - }; - - /// Status for deletion of unique constraints. - enum class DeletionStatus { - SUCCESS, - NOT_FOUND, - EMPTY_PROPERTIES, - PROPERTIES_SIZE_LIMIT_EXCEEDED, - }; - - /// Indexes the given vertex for relevant labels and properties. - /// This method should be called before committing and validating vertices - /// against unique constraints. - /// @throw std::bad_alloc - void UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx); - - /// Creates unique constraint on the given `label` and a list of `properties`. - /// Returns constraint violation if there are multiple vertices with the same - /// label and property values. Returns `CreationStatus::ALREADY_EXISTS` if - /// constraint already existed, `CreationStatus::EMPTY_PROPERTIES` if the - /// given list of properties is empty, - /// `CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the list of properties - /// exceeds the maximum allowed number of properties, and - /// `CreationStatus::SUCCESS` on success. - /// @throw std::bad_alloc - utils::BasicResult<ConstraintViolation, CreationStatus> CreateConstraint(LabelId label, - const std::set<PropertyId> &properties, - utils::SkipList<Vertex>::Accessor vertices); - - /// Deletes the specified constraint. Returns `DeletionStatus::NOT_FOUND` if - /// there is not such constraint in the storage, - /// `DeletionStatus::EMPTY_PROPERTIES` if the given set of `properties` is - /// empty, `DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the given set - /// of `properties` exceeds the maximum allowed number of properties, and - /// `DeletionStatus::SUCCESS` on success. - DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties); - - bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) { - return constraints_.find({label, properties}) != constraints_.end(); - } - - /// Validates the given vertex against unique constraints before committing. - /// This method should be called while commit lock is active with - /// `commit_timestamp` being a potential commit timestamp of the transaction. - /// @throw std::bad_alloc - std::optional<ConstraintViolation> Validate(const Vertex &vertex, const Transaction &tx, - uint64_t commit_timestamp) const; - - std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const; - - /// GC method that removes outdated entries from constraints' storages. - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); - - void Clear() { constraints_.clear(); } - - private: - std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>> constraints_; -}; - -struct Constraints { - std::vector<std::pair<LabelId, PropertyId>> existence_constraints; - UniqueConstraints unique_constraints; -}; - -/// Adds a unique constraint to `constraints`. Returns true if the constraint -/// was successfully added, false if it already exists and a -/// `ConstraintViolation` if there is an existing vertex violating the -/// constraint. -/// -/// @throw std::bad_alloc -/// @throw std::length_error -inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint( - Constraints *constraints, LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices) { - if (utils::Contains(constraints->existence_constraints, std::make_pair(label, property))) { - return false; - } - for (const auto &vertex : vertices) { - if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { - return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}}; - } - } - constraints->existence_constraints.emplace_back(label, property); - return true; -} - -/// Removes a unique constraint from `constraints`. Returns true if the -/// constraint was removed, and false if it doesn't exist. -inline bool DropExistenceConstraint(Constraints *constraints, LabelId label, PropertyId property) { - auto it = std::find(constraints->existence_constraints.begin(), constraints->existence_constraints.end(), - std::make_pair(label, property)); - if (it == constraints->existence_constraints.end()) { - return false; - } - constraints->existence_constraints.erase(it); - return true; -} - -/// Verifies that the given vertex satisfies all existence constraints. Returns -/// `std::nullopt` if all checks pass, and `ConstraintViolation` describing the -/// violated constraint otherwise. -[[nodiscard]] inline std::optional<ConstraintViolation> ValidateExistenceConstraints(const Vertex &vertex, - const Constraints &constraints) { - for (const auto &[label, property] : constraints.existence_constraints) { - if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { - return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}}; - } - } - return std::nullopt; -} - -/// Returns a list of all created existence constraints. -inline std::vector<std::pair<LabelId, PropertyId>> ListExistenceConstraints(const Constraints &constraints) { - return constraints.existence_constraints; -} - -} // namespace memgraph::storage diff --git a/src/storage/v2/constraints/constraint_violation.hpp b/src/storage/v2/constraints/constraint_violation.hpp new file mode 100644 index 000000000..40c8fd657 --- /dev/null +++ b/src/storage/v2/constraints/constraint_violation.hpp @@ -0,0 +1,39 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <set> + +#include "storage/v2/id_types.hpp" + +namespace memgraph::storage { + +struct ConstraintViolation { + enum class Type { + EXISTENCE, + UNIQUE, + }; + + Type type; + LabelId label; + + // While multiple properties are supported by unique constraints, the + // `properties` set will always have exactly one element in the case of + // existence constraint violation. + std::set<PropertyId> properties; +}; + +inline bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs) { + return lhs.type == rhs.type && lhs.label == rhs.label && lhs.properties == rhs.properties; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/constraints/constraints.hpp b/src/storage/v2/constraints/constraints.hpp new file mode 100644 index 000000000..665c5518b --- /dev/null +++ b/src/storage/v2/constraints/constraints.hpp @@ -0,0 +1,48 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/config.hpp" +#include "storage/v2/constraints/existence_constraints.hpp" +#include "storage/v2/disk/unique_constraints.hpp" +#include "storage/v2/inmemory/unique_constraints.hpp" +#include "storage/v2/storage_mode.hpp" + +namespace memgraph::storage { + +struct Constraints { + Constraints(const Config &config, StorageMode storage_mode) { + std::invoke([this, config, storage_mode]() { + existence_constraints_ = std::make_unique<ExistenceConstraints>(); + switch (storage_mode) { + case StorageMode::IN_MEMORY_TRANSACTIONAL: + case StorageMode::IN_MEMORY_ANALYTICAL: + unique_constraints_ = std::make_unique<InMemoryUniqueConstraints>(); + break; + case StorageMode::ON_DISK_TRANSACTIONAL: + unique_constraints_ = std::make_unique<DiskUniqueConstraints>(config); + break; + }; + }); + } + + Constraints(const Constraints &) = delete; + Constraints(Constraints &&) = delete; + Constraints &operator=(const Constraints &) = delete; + Constraints &operator=(Constraints &&) = delete; + ~Constraints() = default; + + std::unique_ptr<ExistenceConstraints> existence_constraints_; + std::unique_ptr<UniqueConstraints> unique_constraints_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/constraints/existence_constraints.cpp b/src/storage/v2/constraints/existence_constraints.cpp new file mode 100644 index 000000000..692dddbb6 --- /dev/null +++ b/src/storage/v2/constraints/existence_constraints.cpp @@ -0,0 +1,58 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/constraints/existence_constraints.hpp" +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/mvcc.hpp" +#include "utils/logging.hpp" + +namespace memgraph::storage { + +bool ExistenceConstraints::ConstraintExists(LabelId label, PropertyId property) const { + return utils::Contains(constraints_, std::make_pair(label, property)); +} + +void ExistenceConstraints::InsertConstraint(LabelId label, PropertyId property) { + if (ConstraintExists(label, property)) { + return; + } + constraints_.emplace_back(label, property); +} + +bool ExistenceConstraints::DropConstraint(LabelId label, PropertyId property) { + auto it = std::find(constraints_.begin(), constraints_.end(), std::make_pair(label, property)); + if (it == constraints_.end()) { + return false; + } + constraints_.erase(it); + return true; +} + +std::vector<std::pair<LabelId, PropertyId>> ExistenceConstraints::ListConstraints() const { return constraints_; } + +[[nodiscard]] std::optional<ConstraintViolation> ExistenceConstraints::Validate(const Vertex &vertex) { + for (const auto &[label, property] : constraints_) { + if (auto violation = ValidateVertexOnConstraint(vertex, label, property); violation.has_value()) { + return violation; + } + } + return std::nullopt; +} + +void ExistenceConstraints::LoadExistenceConstraints(const std::vector<std::string> &keys) { + for (const auto &key : keys) { + const std::vector<std::string> parts = utils::Split(key, ","); + constraints_.emplace_back(LabelId::FromUint(std::stoull(parts[0])), PropertyId::FromUint(std::stoull(parts[1]))); + } +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/constraints/existence_constraints.hpp b/src/storage/v2/constraints/existence_constraints.hpp new file mode 100644 index 000000000..77f7bc43a --- /dev/null +++ b/src/storage/v2/constraints/existence_constraints.hpp @@ -0,0 +1,62 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <optional> + +#include "storage/v2/constraints/constraint_violation.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::storage { + +class ExistenceConstraints { + public: + [[nodiscard]] static std::optional<ConstraintViolation> ValidateVertexOnConstraint(const Vertex &vertex, + LabelId label, + PropertyId property) { + if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { + return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}}; + } + return std::nullopt; + } + + [[nodiscard]] static std::optional<ConstraintViolation> ValidateVerticesOnConstraint( + utils::SkipList<Vertex>::Accessor vertices, LabelId label, PropertyId property) { + for (const auto &vertex : vertices) { + if (auto violation = ValidateVertexOnConstraint(vertex, label, property); violation.has_value()) { + return violation; + } + } + return std::nullopt; + } + + bool ConstraintExists(LabelId label, PropertyId property) const; + + void InsertConstraint(LabelId label, PropertyId property); + + /// Returns true if the constraint was removed, and false if it doesn't exist. + bool DropConstraint(LabelId label, PropertyId property); + + /// Returns `std::nullopt` if all checks pass, and `ConstraintViolation` describing the violated constraint + /// otherwise. + [[nodiscard]] std::optional<ConstraintViolation> Validate(const Vertex &vertex); + + std::vector<std::pair<LabelId, PropertyId>> ListConstraints() const; + + void LoadExistenceConstraints(const std::vector<std::string> &keys); + + private: + std::vector<std::pair<LabelId, PropertyId>> constraints_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/constraints/unique_constraints.hpp b/src/storage/v2/constraints/unique_constraints.hpp new file mode 100644 index 000000000..b9ec04bfc --- /dev/null +++ b/src/storage/v2/constraints/unique_constraints.hpp @@ -0,0 +1,75 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <set> + +#include "storage/v2/constraints/constraint_violation.hpp" +#include "storage/v2/transaction.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/result.hpp" + +namespace memgraph::storage { + +// NOLINTNEXTLINE(misc-definitions-in-headers) +const size_t kUniqueConstraintsMaxProperties = 32; + +class UniqueConstraints { + public: + UniqueConstraints() = default; + UniqueConstraints(const UniqueConstraints &) = delete; + UniqueConstraints(UniqueConstraints &&) = delete; + UniqueConstraints &operator=(const UniqueConstraints &) = delete; + UniqueConstraints &operator=(UniqueConstraints &&) = delete; + virtual ~UniqueConstraints() = default; + + enum class CreationStatus { + SUCCESS, + ALREADY_EXISTS, + EMPTY_PROPERTIES, + PROPERTIES_SIZE_LIMIT_EXCEEDED, + }; + + enum class DeletionStatus { + SUCCESS, + NOT_FOUND, + EMPTY_PROPERTIES, + PROPERTIES_SIZE_LIMIT_EXCEEDED, + }; + + virtual DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties) = 0; + + virtual bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) const = 0; + + virtual void UpdateOnRemoveLabel(LabelId removed_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) = 0; + + virtual void UpdateOnAddLabel(LabelId added_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) = 0; + + virtual std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const = 0; + + virtual void Clear() = 0; + + protected: + static DeletionStatus CheckPropertiesBeforeDeletion(const std::set<PropertyId> &properties) { + if (properties.empty()) { + return UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES; + } + if (properties.size() > kUniqueConstraintsMaxProperties) { + return UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED; + } + return UniqueConstraints::DeletionStatus::SUCCESS; + } +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/delta.hpp b/src/storage/v2/delta.hpp index 5aad1ab2f..c23f06298 100644 --- a/src/storage/v2/delta.hpp +++ b/src/storage/v2/delta.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,6 +12,7 @@ #pragma once #include <atomic> +#include <cstdint> #include "storage/v2/edge_ref.hpp" #include "storage/v2/id_types.hpp" @@ -122,7 +123,10 @@ inline bool operator!=(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer struct Delta { enum class Action { - // Used for both Vertex and Edge + /// Use for Vertex and Edge + /// Used for disk storage for modifying MVCC logic and storing old key. Storing old key is necessary for + /// deleting old-data (compaction). + DELETE_DESERIALIZED_OBJECT, DELETE_OBJECT, RECREATE_OBJECT, SET_PROPERTY, @@ -137,6 +141,7 @@ struct Delta { }; // Used for both Vertex and Edge + struct DeleteDeserializedObjectTag {}; struct DeleteObjectTag {}; struct RecreateObjectTag {}; struct SetPropertyTag {}; @@ -149,44 +154,48 @@ struct Delta { struct RemoveInEdgeTag {}; struct RemoveOutEdgeTag {}; - Delta(DeleteObjectTag, std::atomic<uint64_t> *timestamp, uint64_t command_id) + Delta(DeleteDeserializedObjectTag /*tag*/, std::atomic<uint64_t> *timestamp, + const std::optional<std::string> &old_disk_key) + : action(Action::DELETE_DESERIALIZED_OBJECT), timestamp(timestamp), command_id(0), old_disk_key(old_disk_key) {} + + Delta(DeleteObjectTag /*tag*/, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {} - Delta(RecreateObjectTag, std::atomic<uint64_t> *timestamp, uint64_t command_id) + Delta(RecreateObjectTag /*tag*/, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::RECREATE_OBJECT), timestamp(timestamp), command_id(command_id) {} - Delta(AddLabelTag, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id) + Delta(AddLabelTag /*tag*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::ADD_LABEL), timestamp(timestamp), command_id(command_id), label(label) {} - Delta(RemoveLabelTag, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id) + Delta(RemoveLabelTag /*tag*/, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::REMOVE_LABEL), timestamp(timestamp), command_id(command_id), label(label) {} - Delta(SetPropertyTag, PropertyId key, const PropertyValue &value, std::atomic<uint64_t> *timestamp, + Delta(SetPropertyTag /*tag*/, PropertyId key, const PropertyValue &value, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::SET_PROPERTY), timestamp(timestamp), command_id(command_id), property({key, value}) {} - Delta(AddInEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, + Delta(AddInEdgeTag /*tag*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::ADD_IN_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} - Delta(AddOutEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, + Delta(AddOutEdgeTag /*tag*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::ADD_OUT_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} - Delta(RemoveInEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, + Delta(RemoveInEdgeTag /*tag*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::REMOVE_IN_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} - Delta(RemoveOutEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, + Delta(RemoveOutEdgeTag /*tag*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::REMOVE_OUT_EDGE), timestamp(timestamp), @@ -209,6 +218,9 @@ struct Delta { case Action::REMOVE_IN_EDGE: case Action::REMOVE_OUT_EDGE: break; + case Action::DELETE_DESERIALIZED_OBJECT: + old_disk_key.reset(); + break; case Action::SET_PROPERTY: property.value.~PropertyValue(); break; @@ -224,6 +236,7 @@ struct Delta { std::atomic<Delta *> next{nullptr}; union { + std::optional<std::string> old_disk_key; LabelId label; struct { PropertyId key; diff --git a/src/storage/v2/disk/label_index.cpp b/src/storage/v2/disk/label_index.cpp new file mode 100644 index 000000000..1046405eb --- /dev/null +++ b/src/storage/v2/disk/label_index.cpp @@ -0,0 +1,215 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <rocksdb/options.h> +#include <rocksdb/utilities/transaction.h> + +#include "storage/v2/disk/label_index.hpp" +#include "utils/disk_utils.hpp" +#include "utils/rocksdb_serialization.hpp" + +namespace memgraph::storage { + +namespace { + +[[nodiscard]] bool ClearTransactionEntriesWithRemovedIndexingLabel( + rocksdb::Transaction &disk_transaction, const std::map<Gid, std::vector<LabelId>> &transaction_entries) { + for (const auto &[vertex_gid, labels] : transaction_entries) { + for (const auto &indexing_label : labels) { + if (auto status = disk_transaction.Delete(utils::SerializeVertexAsKeyForLabelIndex(indexing_label, vertex_gid)); + !status.ok()) { + return false; + } + } + } + return true; +} + +/// TODO: duplication with label_property_index.cpp +bool CommitWithTimestamp(rocksdb::Transaction *disk_transaction, uint64_t commit_ts) { + disk_transaction->SetCommitTimestamp(commit_ts); + const auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); +} + +} // namespace + +DiskLabelIndex::DiskLabelIndex(Indices *indices, Constraints *constraints, const Config &config) + : LabelIndex(indices, constraints, config) { + utils::EnsureDirOrDie(config.disk.label_index_directory); + kvstore_ = std::make_unique<RocksDBStorage>(); + kvstore_->options_.create_if_missing = true; + kvstore_->options_.comparator = new ComparatorWithU64TsImpl(); + logging::AssertRocksDBStatus(rocksdb::TransactionDB::Open(kvstore_->options_, rocksdb::TransactionDBOptions(), + config.disk.label_index_directory, &kvstore_->db_)); +} + +bool DiskLabelIndex::CreateIndex(LabelId label, const std::vector<std::pair<std::string, std::string>> &vertices) { + if (!index_.emplace(label).second) { + return false; + } + + auto disk_transaction = CreateRocksDBTransaction(); + for (const auto &[key, value] : vertices) { + disk_transaction->Put(key, value); + } + return CommitWithTimestamp(disk_transaction.get(), 0); +} + +std::unique_ptr<rocksdb::Transaction> DiskLabelIndex::CreateRocksDBTransaction() const { + return std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); +} + +std::unique_ptr<rocksdb::Transaction> DiskLabelIndex::CreateAllReadingRocksDBTransaction() const { + auto tx = CreateRocksDBTransaction(); + tx->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + return tx; +} + +bool DiskLabelIndex::SyncVertexToLabelIndexStorage(const Vertex &vertex, uint64_t commit_timestamp) const { + auto disk_transaction = CreateRocksDBTransaction(); + + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (!disk_transaction->Delete(maybe_old_disk_key.value()).ok()) { + return false; + } + } + + for (const LabelId index_label : index_) { + if (!utils::Contains(vertex.labels, index_label)) { + continue; + } + if (!disk_transaction + ->Put(utils::SerializeVertexAsKeyForLabelIndex(index_label, vertex.gid), + utils::SerializeVertexAsValueForLabelIndex(index_label, vertex.labels, vertex.properties)) + .ok()) { + return false; + } + } + + return CommitWithTimestamp(disk_transaction.get(), commit_timestamp); +} + +/// TODO: this can probably be optimized +bool DiskLabelIndex::ClearDeletedVertex(std::string_view gid, uint64_t transaction_commit_timestamp) const { + auto disk_transaction = CreateAllReadingRocksDBTransaction(); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (std::string key = it->key().ToString(); gid == utils::ExtractGidFromLabelIndexStorage(key)) { + if (!disk_transaction->Delete(key).ok()) { + return false; + } + } + } + + return CommitWithTimestamp(disk_transaction.get(), transaction_commit_timestamp); +} + +bool DiskLabelIndex::DeleteVerticesWithRemovedIndexingLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp) { + auto disk_transaction = CreateAllReadingRocksDBTransaction(); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + bool deletion_success = entries_for_deletion.WithLock( + [transaction_start_timestamp, disk_transaction_ptr = disk_transaction.get()](auto &tx_to_entries_for_deletion) { + if (auto tx_it = tx_to_entries_for_deletion.find(transaction_start_timestamp); + tx_it != tx_to_entries_for_deletion.end()) { + bool res = ClearTransactionEntriesWithRemovedIndexingLabel(*disk_transaction_ptr, tx_it->second); + tx_to_entries_for_deletion.erase(tx_it); + return res; + } + return true; + }); + if (deletion_success) { + return CommitWithTimestamp(disk_transaction.get(), transaction_commit_timestamp); + } + return false; +} + +void DiskLabelIndex::UpdateOnAddLabel(LabelId added_label, Vertex *vertex_before_update, const Transaction &tx) { + entries_for_deletion.WithLock([added_label, vertex_before_update, &tx](auto &tx_to_entries_for_deletion) { + auto tx_it = tx_to_entries_for_deletion.find(tx.start_timestamp); + if (tx_it == tx_to_entries_for_deletion.end()) { + return; + } + auto vertex_label_index_it = tx_it->second.find(vertex_before_update->gid); + if (vertex_label_index_it == tx_it->second.end()) { + return; + } + std::erase_if(vertex_label_index_it->second, + [added_label](const LabelId &indexed_label) { return indexed_label == added_label; }); + }); +} + +void DiskLabelIndex::UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_before_update, const Transaction &tx) { + if (!IndexExists(removed_label)) { + return; + } + entries_for_deletion.WithLock([&removed_label, &tx, vertex_before_update](auto &tx_to_entries_for_deletion) { + auto [it, _] = tx_to_entries_for_deletion.emplace( + std::piecewise_construct, std::forward_as_tuple(tx.start_timestamp), std::forward_as_tuple()); + auto &vertex_map_store = it->second; + auto [it_vertex_map_store, emplaced] = vertex_map_store.emplace( + std::piecewise_construct, std::forward_as_tuple(vertex_before_update->gid), std::forward_as_tuple()); + it_vertex_map_store->second.emplace_back(removed_label); + }); +} + +/// TODO: andi Here will come Bloom filter deletion +bool DiskLabelIndex::DropIndex(LabelId label) { + if (!(index_.erase(label) > 0)) { + return false; + } + auto disk_transaction = CreateAllReadingRocksDBTransaction(); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + std::string key = it->key().ToString(); + if (key.starts_with(utils::SerializeIdType(label))) { + disk_transaction->Delete(it->key().ToString()); + } + } + + return CommitWithTimestamp(disk_transaction.get(), 0); +} + +bool DiskLabelIndex::IndexExists(LabelId label) const { return index_.find(label) != index_.end(); } + +std::vector<LabelId> DiskLabelIndex::ListIndices() const { return {index_.begin(), index_.end()}; } + +uint64_t DiskLabelIndex::ApproximateVertexCount(LabelId /*label*/) const { return 10; } + +void DiskLabelIndex::LoadIndexInfo(const std::vector<std::string> &labels) { + for (const std::string &label : labels) { + LabelId label_id = LabelId::FromUint(std::stoull(label)); + index_.insert(label_id); + } +} + +RocksDBStorage *DiskLabelIndex::GetRocksDBStorage() const { return kvstore_.get(); } + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/label_index.hpp b/src/storage/v2/disk/label_index.hpp new file mode 100644 index 000000000..76151ef22 --- /dev/null +++ b/src/storage/v2/disk/label_index.hpp @@ -0,0 +1,62 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <rocksdb/iterator.h> +#include <rocksdb/utilities/transaction.h> + +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/indices/label_index.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/rocksdb_serialization.hpp" + +namespace memgraph::storage { +class DiskLabelIndex : public storage::LabelIndex { + public: + DiskLabelIndex(Indices *indices, Constraints *constraints, const Config &config); + + [[nodiscard]] bool CreateIndex(LabelId label, const std::vector<std::pair<std::string, std::string>> &vertices); + + std::unique_ptr<rocksdb::Transaction> CreateRocksDBTransaction() const; + + std::unique_ptr<rocksdb::Transaction> CreateAllReadingRocksDBTransaction() const; + + [[nodiscard]] bool SyncVertexToLabelIndexStorage(const Vertex &vertex, uint64_t commit_timestamp) const; + + [[nodiscard]] bool ClearDeletedVertex(std::string_view gid, uint64_t transaction_commit_timestamp) const; + + [[nodiscard]] bool DeleteVerticesWithRemovedIndexingLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp); + /// @throw std::bad_alloc + void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_before_update, const Transaction &tx) override; + + void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_before_update, const Transaction &tx) override; + + /// Returns false if there was no index to drop + bool DropIndex(LabelId label) override; + + bool IndexExists(LabelId label) const override; + + std::vector<LabelId> ListIndices() const override; + + uint64_t ApproximateVertexCount(LabelId label) const override; + + RocksDBStorage *GetRocksDBStorage() const; + + void LoadIndexInfo(const std::vector<std::string> &labels); + + private: + utils::Synchronized<std::map<uint64_t, std::map<Gid, std::vector<LabelId>>>> entries_for_deletion; + std::unordered_set<LabelId> index_; + std::unique_ptr<RocksDBStorage> kvstore_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/label_property_index.cpp b/src/storage/v2/disk/label_property_index.cpp new file mode 100644 index 000000000..ad2f8f59f --- /dev/null +++ b/src/storage/v2/disk/label_property_index.cpp @@ -0,0 +1,228 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +/// TODO: clear dependencies + +#include "storage/v2/disk/label_property_index.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/indices_utils.hpp" +#include "storage/v2/property_value.hpp" +#include "utils/disk_utils.hpp" +#include "utils/exceptions.hpp" +#include "utils/file.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::storage { + +namespace { + +bool IsVertexIndexedByLabelProperty(const Vertex &vertex, LabelId label, PropertyId property) { + return utils::Contains(vertex.labels, label) && vertex.properties.HasProperty(property); +} + +[[nodiscard]] bool ClearTransactionEntriesWithRemovedIndexingLabel( + rocksdb::Transaction &disk_transaction, + const std::map<Gid, std::vector<std::pair<LabelId, PropertyId>>> &transaction_entries) { + for (const auto &[vertex_gid, index] : transaction_entries) { + for (const auto &[indexing_label, indexing_property] : index) { + if (auto status = disk_transaction.Delete( + utils::SerializeVertexAsKeyForLabelPropertyIndex(indexing_label, indexing_property, vertex_gid)); + !status.ok()) { + return false; + } + } + } + return true; +} + +bool CommitWithTimestamp(rocksdb::Transaction *disk_transaction, uint64_t commit_ts) { + disk_transaction->SetCommitTimestamp(commit_ts); + const auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); +} + +} // namespace + +DiskLabelPropertyIndex::DiskLabelPropertyIndex(Indices *indices, Constraints *constraints, const Config &config) + : LabelPropertyIndex(indices, constraints, config) { + utils::EnsureDirOrDie(config.disk.label_property_index_directory); + kvstore_ = std::make_unique<RocksDBStorage>(); + kvstore_->options_.create_if_missing = true; + kvstore_->options_.comparator = new ComparatorWithU64TsImpl(); + logging::AssertRocksDBStatus(rocksdb::TransactionDB::Open( + kvstore_->options_, rocksdb::TransactionDBOptions(), config.disk.label_property_index_directory, &kvstore_->db_)); +} + +bool DiskLabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, + const std::vector<std::pair<std::string, std::string>> &vertices) { + if (!index_.emplace(label, property).second) { + return false; + } + + auto disk_transaction = CreateRocksDBTransaction(); + for (const auto &[key, value] : vertices) { + disk_transaction->Put(key, value); + } + + return CommitWithTimestamp(disk_transaction.get(), 0); +} + +std::unique_ptr<rocksdb::Transaction> DiskLabelPropertyIndex::CreateRocksDBTransaction() const { + return std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); +} + +std::unique_ptr<rocksdb::Transaction> DiskLabelPropertyIndex::CreateAllReadingRocksDBTransaction() const { + auto tx = CreateRocksDBTransaction(); + tx->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + return tx; +} + +bool DiskLabelPropertyIndex::SyncVertexToLabelPropertyIndexStorage(const Vertex &vertex, + uint64_t commit_timestamp) const { + auto disk_transaction = CreateRocksDBTransaction(); + + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (!disk_transaction->Delete(maybe_old_disk_key.value()).ok()) { + return false; + } + } + for (const auto &[index_label, index_property] : index_) { + if (IsVertexIndexedByLabelProperty(vertex, index_label, index_property)) { + if (!disk_transaction + ->Put(utils::SerializeVertexAsKeyForLabelPropertyIndex(index_label, index_property, vertex.gid), + utils::SerializeVertexAsValueForLabelPropertyIndex(index_label, vertex.labels, vertex.properties)) + .ok()) { + return false; + } + } + } + return CommitWithTimestamp(disk_transaction.get(), commit_timestamp); +} + +bool DiskLabelPropertyIndex::ClearDeletedVertex(std::string_view gid, uint64_t transaction_commit_timestamp) const { + auto disk_transaction = CreateAllReadingRocksDBTransaction(); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (std::string key = it->key().ToString(); gid == utils::ExtractGidFromLabelPropertyIndexStorage(key)) { + if (!disk_transaction->Delete(key).ok()) { + return false; + } + } + } + return CommitWithTimestamp(disk_transaction.get(), transaction_commit_timestamp); +} + +bool DiskLabelPropertyIndex::DeleteVerticesWithRemovedIndexingLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp) { + auto disk_transaction = CreateAllReadingRocksDBTransaction(); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + bool deletion_success = entries_for_deletion.WithLock( + [transaction_start_timestamp, disk_transaction_ptr = disk_transaction.get()](auto &tx_to_entries_for_deletion) { + if (auto tx_it = tx_to_entries_for_deletion.find(transaction_start_timestamp); + tx_it != tx_to_entries_for_deletion.end()) { + bool res = ClearTransactionEntriesWithRemovedIndexingLabel(*disk_transaction_ptr, tx_it->second); + tx_to_entries_for_deletion.erase(tx_it); + return res; + } + return true; + }); + if (deletion_success) { + return CommitWithTimestamp(disk_transaction.get(), transaction_commit_timestamp); + } + return false; +} + +void DiskLabelPropertyIndex::UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) { + entries_for_deletion.WithLock([added_label, vertex_after_update, &tx](auto &tx_to_entries_for_deletion) { + auto tx_it = tx_to_entries_for_deletion.find(tx.start_timestamp); + if (tx_it == tx_to_entries_for_deletion.end()) { + return; + } + auto vertex_label_index_it = tx_it->second.find(vertex_after_update->gid); + if (vertex_label_index_it == tx_it->second.end()) { + return; + } + std::erase_if(vertex_label_index_it->second, + [added_label](const std::pair<LabelId, PropertyId> &index) { return index.first == added_label; }); + }); +} + +void DiskLabelPropertyIndex::UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_after_update, + const Transaction &tx) { + for (const auto &index_entry : index_) { + if (index_entry.first != removed_label) { + continue; + } + entries_for_deletion.WithLock([&index_entry, &tx, vertex_after_update](auto &tx_to_entries_for_deletion) { + const auto &[indexing_label, indexing_property] = index_entry; + auto [it, _] = tx_to_entries_for_deletion.emplace( + std::piecewise_construct, std::forward_as_tuple(tx.start_timestamp), std::forward_as_tuple()); + auto &vertex_map_store = it->second; + auto [it_vertex_map_store, emplaced] = vertex_map_store.emplace( + std::piecewise_construct, std::forward_as_tuple(vertex_after_update->gid), std::forward_as_tuple()); + it_vertex_map_store->second.emplace_back(indexing_label, indexing_property); + }); + } +} + +/// TODO: andi If stays the same, move it to the hpp +void DiskLabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) {} + +bool DiskLabelPropertyIndex::DropIndex(LabelId label, PropertyId property) { + return index_.erase({label, property}) > 0; +} + +bool DiskLabelPropertyIndex::IndexExists(LabelId label, PropertyId property) const { + return utils::Contains(index_, std::make_pair(label, property)); +} + +std::vector<std::pair<LabelId, PropertyId>> DiskLabelPropertyIndex::ListIndices() const { + return {index_.begin(), index_.end()}; +} + +uint64_t DiskLabelPropertyIndex::ApproximateVertexCount(LabelId /*label*/, PropertyId /*property*/) const { return 10; } + +uint64_t DiskLabelPropertyIndex::ApproximateVertexCount(LabelId /*label*/, PropertyId /*property*/, + const PropertyValue & /*value*/) const { + return 10; +} + +uint64_t DiskLabelPropertyIndex::ApproximateVertexCount( + LabelId /*label*/, PropertyId /*property*/, const std::optional<utils::Bound<PropertyValue>> & /*lower*/, + const std::optional<utils::Bound<PropertyValue>> & /*upper*/) const { + return 10; +} + +void DiskLabelPropertyIndex::LoadIndexInfo(const std::vector<std::string> &keys) { + for (const auto &label_property : keys) { + std::vector<std::string> label_property_split = utils::Split(label_property, ","); + index_.emplace(std::make_pair(LabelId::FromUint(std::stoull(label_property_split[0])), + PropertyId::FromUint(std::stoull(label_property_split[1])))); + } +} + +RocksDBStorage *DiskLabelPropertyIndex::GetRocksDBStorage() const { return kvstore_.get(); } + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/label_property_index.hpp b/src/storage/v2/disk/label_property_index.hpp new file mode 100644 index 000000000..9be8287f7 --- /dev/null +++ b/src/storage/v2/disk/label_property_index.hpp @@ -0,0 +1,71 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/indices/label_property_index.hpp" + +namespace memgraph::storage { + +/// TODO: andi. Too many copies, extract at one place +using ParalellizedIndexCreationInfo = + std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>; + +class DiskLabelPropertyIndex : public storage::LabelPropertyIndex { + public: + DiskLabelPropertyIndex(Indices *indices, Constraints *constraints, const Config &config); + + bool CreateIndex(LabelId label, PropertyId property, + const std::vector<std::pair<std::string, std::string>> &vertices); + + std::unique_ptr<rocksdb::Transaction> CreateRocksDBTransaction() const; + + std::unique_ptr<rocksdb::Transaction> CreateAllReadingRocksDBTransaction() const; + + [[nodiscard]] bool SyncVertexToLabelPropertyIndexStorage(const Vertex &vertex, uint64_t commit_timestamp) const; + + [[nodiscard]] bool ClearDeletedVertex(std::string_view gid, uint64_t transaction_commit_timestamp) const; + + [[nodiscard]] bool DeleteVerticesWithRemovedIndexingLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp); + + void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) override; + + void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_after_update, const Transaction &tx) override; + + void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) override; + + bool DropIndex(LabelId label, PropertyId property) override; + + bool IndexExists(LabelId label, PropertyId property) const override; + + std::vector<std::pair<LabelId, PropertyId>> ListIndices() const override; + + uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override; + + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override; + + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const override; + + RocksDBStorage *GetRocksDBStorage() const; + + void LoadIndexInfo(const std::vector<std::string> &keys); + + private: + utils::Synchronized<std::map<uint64_t, std::map<Gid, std::vector<std::pair<LabelId, PropertyId>>>>> + entries_for_deletion; + std::set<std::pair<LabelId, PropertyId>> index_; + std::unique_ptr<RocksDBStorage> kvstore_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/name_id_mapper.hpp b/src/storage/v2/disk/name_id_mapper.hpp new file mode 100644 index 000000000..89d23993c --- /dev/null +++ b/src/storage/v2/disk/name_id_mapper.hpp @@ -0,0 +1,111 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <atomic> +#include <cstddef> +#include <filesystem> +#include <memory> +#include <string> +#include <string_view> + +#include "kvstore/kvstore.hpp" +#include "spdlog/spdlog.h" +#include "storage/v2/name_id_mapper.hpp" +#include "utils/logging.hpp" +#include "utils/string.hpp" + +namespace memgraph::storage { + +/// Implements class adapter. Object adapters are usually better but here we need access to protected members +/// of base class and we don't want to make them public. Also, from the performance perspective, it doesn't matter +/// since we either have dynamic virtual dispatch here or on storage level. +class DiskNameIdMapper final : public NameIdMapper { + public: + explicit DiskNameIdMapper(std::filesystem::path name_to_id_path, std::filesystem::path id_to_name_path) + : name_to_id_storage_(std::make_unique<kvstore::KVStore>(name_to_id_path)), + id_to_name_storage_(std::make_unique<kvstore::KVStore>(id_to_name_path)) { + InitializeFromDisk(); + } + + uint64_t NameToId(const std::string_view name) override { + if (auto maybe_id = MaybeNameToId(name); maybe_id.has_value()) { + return maybe_id.value(); + } + uint64_t res_id = 0; + if (auto maybe_id_from_disk = name_to_id_storage_->Get(std::string(name)); maybe_id_from_disk.has_value()) { + res_id = std::stoull(maybe_id_from_disk.value()); + InsertNameIdEntryToCache(std::string(name), res_id); + InsertIdNameEntryToCache(res_id, std::string(name)); + } else { + res_id = NameIdMapper::NameToId(name); + MG_ASSERT(id_to_name_storage_->Put(std::to_string(res_id), std::string(name)), + "Failed to store id to name to disk!"); + MG_ASSERT(name_to_id_storage_->Put(std::string(name), std::to_string(res_id)), + "Failed to store id to name to disk!"); + } + + return res_id; + } + + const std::string &IdToName(uint64_t id) override { + auto maybe_name = NameIdMapper::MaybeIdToName(id); + if (maybe_name.has_value()) { + return maybe_name.value(); + } + + auto maybe_name_from_disk = id_to_name_storage_->Get(std::to_string(id)); + MG_ASSERT(maybe_name_from_disk.has_value(), "Trying to get a name from disk for an invalid ID!"); + + InsertIdNameEntryToCache(id, maybe_name_from_disk.value()); + return InsertNameIdEntryToCache(maybe_name_from_disk.value(), id); + } + + private: + std::optional<std::reference_wrapper<const uint64_t>> MaybeNameToId(const std::string_view name) const { + auto name_to_id_acc = name_to_id_.access(); + auto result = name_to_id_acc.find(name); + if (result == name_to_id_acc.end()) { + return std::nullopt; + } + return result->id; + } + + const std::string &InsertNameIdEntryToCache(const std::string &name, uint64_t id) { + auto name_to_id_acc = name_to_id_.access(); + return name_to_id_acc.insert({std::string(name), id}).first->name; + } + + const std::string &InsertIdNameEntryToCache(uint64_t id, const std::string &name) { + auto id_to_name_acc = id_to_name_.access(); + return id_to_name_acc.insert({id, std::string(name)}).first->name; + } + + void InitializeFromDisk() { + for (auto itr = name_to_id_storage_->begin(); itr != name_to_id_storage_->end(); ++itr) { + auto name = itr->first; + auto id = std::stoull(itr->second); + InsertNameIdEntryToCache(name, id); + counter_.fetch_add(1, std::memory_order_release); + } + for (auto itr = id_to_name_storage_->begin(); itr != id_to_name_storage_->end(); ++itr) { + auto id = std::stoull(itr->first); + auto name = itr->second; + InsertIdNameEntryToCache(id, name); + } + } + + std::unique_ptr<kvstore::KVStore> name_to_id_storage_; + std::unique_ptr<kvstore::KVStore> id_to_name_storage_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/rocksdb_storage.cpp b/src/storage/v2/disk/rocksdb_storage.cpp new file mode 100644 index 000000000..61c5bd97d --- /dev/null +++ b/src/storage/v2/disk/rocksdb_storage.cpp @@ -0,0 +1,83 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "rocksdb_storage.hpp" +#include <string_view> +#include "utils/rocksdb_serialization.hpp" + +namespace memgraph::storage { + +namespace { + +inline rocksdb::Slice StripTimestampFromUserKey(const rocksdb::Slice &user_key, size_t ts_sz) { + rocksdb::Slice ret = user_key; + ret.remove_suffix(ts_sz); + return ret; +} + +/// NOTE: Timestamp is encoded as last 8B in user key. +inline rocksdb::Slice ExtractTimestampFromUserKey(const rocksdb::Slice &user_key) { + assert(user_key.size() >= sizeof(uint64_t)); + return {user_key.data() + user_key.size() - sizeof(uint64_t), sizeof(uint64_t)}; +} + +// Extracts global id from user key. User key must be without timestamp. +std::string_view ExtractGidFromUserKey(const rocksdb::Slice &key) { + assert(key.size() >= 2); + auto keyStrView = key.ToStringView(); + return keyStrView.substr(keyStrView.find_last_of('|') + 1); +} + +} // namespace + +ComparatorWithU64TsImpl::ComparatorWithU64TsImpl() + : Comparator(/*ts_sz=*/sizeof(uint64_t)), cmp_without_ts_(rocksdb::BytewiseComparator()) { + assert(cmp_without_ts_->timestamp_size() == 0); +} + +int ComparatorWithU64TsImpl::Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const { + int ret = CompareWithoutTimestamp(a, b); + if (ret != 0) { + return ret; + } + // Compare timestamp. + // For the same user key with different timestamps, larger (newer) timestamp + // comes first. + return CompareTimestamp(ExtractTimestampFromUserKey(b), ExtractTimestampFromUserKey(a)); +} + +int ComparatorWithU64TsImpl::CompareWithoutTimestamp(const rocksdb::Slice &a, bool a_has_ts, const rocksdb::Slice &b, + bool b_has_ts) const { + const size_t ts_sz = timestamp_size(); + assert(!a_has_ts || a.size() >= ts_sz); + assert(!b_has_ts || b.size() >= ts_sz); + rocksdb::Slice lhsUserKey = a_has_ts ? StripTimestampFromUserKey(a, ts_sz) : a; + rocksdb::Slice rhsUserKey = b_has_ts ? StripTimestampFromUserKey(b, ts_sz) : b; + rocksdb::Slice lhsGid = ExtractGidFromUserKey(lhsUserKey); + rocksdb::Slice rhsGid = ExtractGidFromUserKey(rhsUserKey); + return cmp_without_ts_->Compare(lhsGid, rhsGid); +} + +int ComparatorWithU64TsImpl::CompareTimestamp(const rocksdb::Slice &ts1, const rocksdb::Slice &ts2) const { + assert(ts1.size() == sizeof(uint64_t)); + assert(ts2.size() == sizeof(uint64_t)); + uint64_t lhs = utils::DecodeFixed64(ts1.data()); + uint64_t rhs = utils::DecodeFixed64(ts2.data()); + if (lhs < rhs) { + return -1; + } + if (lhs > rhs) { + return 1; + } + return 0; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/rocksdb_storage.hpp b/src/storage/v2/disk/rocksdb_storage.hpp new file mode 100644 index 000000000..b55e139ca --- /dev/null +++ b/src/storage/v2/disk/rocksdb_storage.hpp @@ -0,0 +1,89 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <rocksdb/comparator.h> +#include <rocksdb/db.h> +#include <rocksdb/iterator.h> +#include <rocksdb/options.h> +#include <rocksdb/status.h> +#include <rocksdb/utilities/transaction_db.h> + +#include "storage/v2/id_types.hpp" +#include "storage/v2/property_store.hpp" +#include "utils/logging.hpp" + +namespace memgraph::storage { + +/// TODO: this should be somehow more wrapped inside the storage class so from the software engineering perspective +/// it isn't great to have this here. But for now it is ok. +/// Wraps RocksDB objects inside a struct. Vertex_chandle and edge_chandle are column family handles that may be +/// nullptr. In that case client should take care about them. +struct RocksDBStorage { + explicit RocksDBStorage() {} + + RocksDBStorage(const RocksDBStorage &) = delete; + RocksDBStorage &operator=(const RocksDBStorage &) = delete; + RocksDBStorage(RocksDBStorage &&) = delete; + RocksDBStorage &operator=(RocksDBStorage &&) = delete; + + ~RocksDBStorage() { + delete db_; + db_ = nullptr; + delete options_.comparator; + options_.comparator = nullptr; + } + + rocksdb::Options options_; + rocksdb::TransactionDB *db_; + rocksdb::ColumnFamilyHandle *vertex_chandle = nullptr; + rocksdb::ColumnFamilyHandle *edge_chandle = nullptr; + rocksdb::ColumnFamilyHandle *default_chandle = nullptr; + + uint64_t ApproximateVertexCount() const { + uint64_t estimate_num_keys = 0; + db_->GetIntProperty(vertex_chandle, "rocksdb.estimate-num-keys", &estimate_num_keys); + return estimate_num_keys; + } + + uint64_t ApproximateEdgeCount() const { + uint64_t estimate_num_keys = 0; + db_->GetIntProperty(edge_chandle, "rocksdb.estimate-num-keys", &estimate_num_keys); + return estimate_num_keys; + } +}; + +/// RocksDB comparator that compares keys with timestamps. +class ComparatorWithU64TsImpl : public rocksdb::Comparator { + public: + explicit ComparatorWithU64TsImpl(); + + static const char *kClassName() { return "be"; } + + const char *Name() const override { return kClassName(); } + + void FindShortSuccessor(std::string * /*key*/) const override {} + void FindShortestSeparator(std::string * /*start*/, const rocksdb::Slice & /*limit*/) const override {} + + int Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const override; + + using Comparator::CompareWithoutTimestamp; + int CompareWithoutTimestamp(const rocksdb::Slice &a, bool a_has_ts, const rocksdb::Slice &b, + bool b_has_ts) const override; + + int CompareTimestamp(const rocksdb::Slice &ts1, const rocksdb::Slice &ts2) const override; + + private: + const Comparator *cmp_without_ts_{nullptr}; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp new file mode 100644 index 000000000..63a5ad23c --- /dev/null +++ b/src/storage/v2/disk/storage.cpp @@ -0,0 +1,1703 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <limits> +#include <optional> +#include <stdexcept> +#include <vector> + +#include <rocksdb/comparator.h> +#include <rocksdb/db.h> +#include <rocksdb/slice.h> + +#include <rocksdb/options.h> +#include <rocksdb/utilities/transaction.h> +#include <rocksdb/utilities/transaction_db.h> + +#include "kvstore/kvstore.hpp" +#include "spdlog/spdlog.h" +#include "storage/v2/constraints/unique_constraints.hpp" +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/disk/unique_constraints.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/mvcc.hpp" +#include "storage/v2/property_store.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/result.hpp" +#include "storage/v2/storage.hpp" +#include "storage/v2/storage_error.hpp" +#include "storage/v2/transaction.hpp" +#include "storage/v2/vertex_accessor.hpp" +#include "storage/v2/view.hpp" +#include "utils/disk_utils.hpp" +#include "utils/exceptions.hpp" +#include "utils/file.hpp" +#include "utils/memory_tracker.hpp" +#include "utils/message.hpp" +#include "utils/on_scope_exit.hpp" +#include "utils/readable_size.hpp" +#include "utils/result.hpp" +#include "utils/rocksdb_serialization.hpp" +#include "utils/skip_list.hpp" +#include "utils/stat.hpp" +#include "utils/string.hpp" + +namespace memgraph::storage { + +using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; + +namespace { + +constexpr const char *vertexHandle = "vertex"; +constexpr const char *edgeHandle = "edge"; +constexpr const char *defaultHandle = "default"; +constexpr const char *lastTransactionStartTimeStamp = "last_transaction_start_timestamp"; +constexpr const char *label_index_str = "label_index"; +constexpr const char *label_property_index_str = "label_property_index"; +constexpr const char *existence_constraints_str = "existence_constraints"; +constexpr const char *unique_constraints_str = "unique_constraints"; + +bool VertexExistsInCache(const utils::SkipList<Vertex>::Accessor &accessor, Gid gid) { + return accessor.find(gid) != accessor.end(); +} + +bool VertexHasLabel(const Vertex &vertex, LabelId label, Transaction *transaction, View view) { + bool deleted = false; + bool has_label = false; + Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + deleted = vertex.deleted; + has_label = std::find(vertex.labels.begin(), vertex.labels.end(), label) != vertex.labels.end(); + delta = vertex.delta; + } + ApplyDeltasForRead(transaction, delta, view, [&deleted, &has_label, label](const Delta &delta) { + switch (delta.action) { + case Delta::Action::REMOVE_LABEL: { + if (delta.label == label) { + MG_ASSERT(has_label, "Invalid database state!"); + has_label = false; + } + break; + } + case Delta::Action::ADD_LABEL: { + if (delta.label == label) { + MG_ASSERT(!has_label, "Invalid database state!"); + has_label = true; + } + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + 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; + } + }); + return has_label && !deleted; +} + +PropertyValue GetVertexProperty(const Vertex &vertex, PropertyId property, Transaction *transaction, View view) { + bool deleted = false; + PropertyValue value; + Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + deleted = vertex.deleted; + value = vertex.properties.GetProperty(property); + delta = vertex.delta; + } + ApplyDeltasForRead(transaction, delta, view, [&deleted, &value, property](const Delta &delta) { + switch (delta.action) { + case Delta::Action::SET_PROPERTY: { + if (delta.property.key == property) { + value = delta.property.value; + } + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + break; + } + }); + if (deleted) { + return {}; + } + return value; +} + +bool HasVertexProperty(const Vertex &vertex, PropertyId property, Transaction *transaction, View view) { + return !GetVertexProperty(vertex, property, transaction, view).IsNull(); +} + +bool HasVertexEqualPropertyValue(const Vertex &vertex, PropertyId property_id, PropertyValue property_value, + Transaction *transaction, View view) { + return GetVertexProperty(vertex, property_id, transaction, view) == property_value; +} + +bool IsPropertyValueWithinInterval(const PropertyValue &value, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound) { + if (lower_bound && (!PropertyValue::AreComparableTypes(value.type(), lower_bound->value().type()) || + value < lower_bound->value() || (lower_bound->IsExclusive() && value == lower_bound->value()))) { + return false; + } + if (upper_bound && (!PropertyValue::AreComparableTypes(value.type(), upper_bound->value().type()) || + value > upper_bound->value() || (upper_bound->IsExclusive() && value == upper_bound->value()))) { + return false; + } + return true; +} + +} // namespace + +void DiskStorage::LoadTimestampIfExists() { + if (!utils::DirExists(config_.disk.durability_directory)) { + return; + } + if (auto last_timestamp_ = durability_kvstore_->Get(lastTransactionStartTimeStamp); last_timestamp_.has_value()) { + timestamp_ = std::stoull(last_timestamp_.value()); + } +} + +void DiskStorage::LoadIndexInfoIfExists() const { + if (utils::DirExists(config_.disk.durability_directory)) { + LoadLabelIndexInfoIfExists(); + LoadLabelPropertyIndexInfoIfExists(); + } +} + +void DiskStorage::LoadLabelIndexInfoIfExists() const { + if (auto label_index = durability_kvstore_->Get(label_index_str); label_index.has_value()) { + auto *disk_label_index = static_cast<DiskLabelIndex *>(indices_.label_index_.get()); + const std::vector<std::string> labels{utils::Split(label_index.value(), "|")}; + disk_label_index->LoadIndexInfo(labels); + } +} + +void DiskStorage::LoadLabelPropertyIndexInfoIfExists() const { + if (auto label_property_index = durability_kvstore_->Get(label_property_index_str); + label_property_index.has_value()) { + auto *disk_label_property_index = static_cast<DiskLabelPropertyIndex *>(indices_.label_property_index_.get()); + const std::vector<std::string> keys{utils::Split(label_property_index.value(), "|")}; + disk_label_property_index->LoadIndexInfo(keys); + } +} + +void DiskStorage::LoadConstraintsInfoIfExists() const { + if (utils::DirExists(config_.disk.durability_directory)) { + LoadExistenceConstraintInfoIfExists(); + LoadUniqueConstraintInfoIfExists(); + } +} + +void DiskStorage::LoadExistenceConstraintInfoIfExists() const { + if (auto existence_constraints = durability_kvstore_->Get(existence_constraints_str); + existence_constraints.has_value()) { + std::vector<std::string> keys = utils::Split(existence_constraints.value(), "|"); + constraints_.existence_constraints_->LoadExistenceConstraints(keys); + } +} + +void DiskStorage::LoadUniqueConstraintInfoIfExists() const { + if (auto unique_constraints = durability_kvstore_->Get(unique_constraints_str); unique_constraints.has_value()) { + std::vector<std::string> keys = utils::Split(unique_constraints.value(), "|"); + auto *disk_unique_constraints = static_cast<DiskUniqueConstraints *>(constraints_.unique_constraints_.get()); + disk_unique_constraints->LoadUniqueConstraints(keys); + } +} + +DiskStorage::DiskStorage(Config config) + : Storage(config, StorageMode::ON_DISK_TRANSACTIONAL), + kvstore_(std::make_unique<RocksDBStorage>()), + durability_kvstore_(std::make_unique<kvstore::KVStore>(config.disk.durability_directory)) { + LoadTimestampIfExists(); + LoadIndexInfoIfExists(); + LoadConstraintsInfoIfExists(); + kvstore_->options_.create_if_missing = true; + kvstore_->options_.comparator = new ComparatorWithU64TsImpl(); + kvstore_->options_.compression = rocksdb::kNoCompression; + kvstore_->options_.wal_recovery_mode = rocksdb::WALRecoveryMode::kPointInTimeRecovery; + kvstore_->options_.wal_dir = config_.disk.wal_directory; + kvstore_->options_.wal_compression = rocksdb::kNoCompression; + std::vector<rocksdb::ColumnFamilyHandle *> column_handles; + std::vector<rocksdb::ColumnFamilyDescriptor> column_families; + if (utils::DirExists(config.disk.main_storage_directory)) { + column_families.emplace_back(vertexHandle, kvstore_->options_); + column_families.emplace_back(edgeHandle, kvstore_->options_); + column_families.emplace_back(defaultHandle, kvstore_->options_); + + logging::AssertRocksDBStatus(rocksdb::TransactionDB::Open(kvstore_->options_, rocksdb::TransactionDBOptions(), + config.disk.main_storage_directory, column_families, + &column_handles, &kvstore_->db_)); + kvstore_->vertex_chandle = column_handles[0]; + kvstore_->edge_chandle = column_handles[1]; + kvstore_->default_chandle = column_handles[2]; + } else { + logging::AssertRocksDBStatus(rocksdb::TransactionDB::Open(kvstore_->options_, rocksdb::TransactionDBOptions(), + config.disk.main_storage_directory, &kvstore_->db_)); + logging::AssertRocksDBStatus( + kvstore_->db_->CreateColumnFamily(kvstore_->options_, vertexHandle, &kvstore_->vertex_chandle)); + logging::AssertRocksDBStatus( + kvstore_->db_->CreateColumnFamily(kvstore_->options_, edgeHandle, &kvstore_->edge_chandle)); + } +} + +DiskStorage::~DiskStorage() { + durability_kvstore_->Put(lastTransactionStartTimeStamp, std::to_string(timestamp_)); + logging::AssertRocksDBStatus(kvstore_->db_->DestroyColumnFamilyHandle(kvstore_->vertex_chandle)); + logging::AssertRocksDBStatus(kvstore_->db_->DestroyColumnFamilyHandle(kvstore_->edge_chandle)); + if (kvstore_->default_chandle) { + // We must destroy default column family handle only if it was read from existing database. + // https://github.com/facebook/rocksdb/issues/5006#issuecomment-1003154821 + logging::AssertRocksDBStatus(kvstore_->db_->DestroyColumnFamilyHandle(kvstore_->default_chandle)); + } + delete kvstore_->options_.comparator; + kvstore_->options_.comparator = nullptr; +} + +DiskStorage::DiskAccessor::DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode) + : Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) { + rocksdb::WriteOptions write_options; + auto txOptions = rocksdb::TransactionOptions{.set_snapshot = true}; + disk_transaction_ = storage->kvstore_->db_->BeginTransaction(write_options, txOptions); + disk_transaction_->SetReadTimestampForValidation(transaction_.start_timestamp); +} + +DiskStorage::DiskAccessor::DiskAccessor(DiskAccessor &&other) noexcept + : Accessor(std::move(other)), config_(other.config_) { + other.is_transaction_active_ = false; + other.commit_timestamp_.reset(); +} + +DiskStorage::DiskAccessor::~DiskAccessor() { + if (is_transaction_active_) { + Abort(); + } + + FinalizeTransaction(); +} + +/// NOTE: This will create Delta object which will cause deletion of old key entry on the disk +std::optional<storage::VertexAccessor> DiskStorage::DiskAccessor::LoadVertexToMainMemoryCache( + const rocksdb::Slice &key, const rocksdb::Slice &value) { + auto main_storage_accessor = vertices_.access(); + + const std::string key_str = key.ToString(); + storage::Gid gid = Gid::FromUint(std::stoull(utils::ExtractGidFromKey(key_str))); + if (VertexExistsInCache(main_storage_accessor, gid)) { + return std::nullopt; + } + std::vector<LabelId> labels_id = utils::DeserializeLabelsFromMainDiskStorage(key_str); + return CreateVertex(main_storage_accessor, gid, labels_id, + utils::DeserializePropertiesFromMainDiskStorage(value.ToStringView()), + CreateDeleteDeserializedObjectDelta(&transaction_, key.ToString())); +} + +std::optional<storage::VertexAccessor> DiskStorage::DiskAccessor::LoadVertexToLabelIndexCache( + const rocksdb::Slice &key, const rocksdb::Slice &value, Delta *index_delta, + utils::SkipList<storage::Vertex>::Accessor index_accessor) { + storage::Gid gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelIndexStorage(key.ToString()))); + if (VertexExistsInCache(index_accessor, gid)) { + return std::nullopt; + } + + const std::string value_str{value.ToString()}; + const auto labels{utils::DeserializeLabelsFromLabelIndexStorage(value_str)}; + return CreateVertex(index_accessor, gid, labels, utils::DeserializePropertiesFromLabelIndexStorage(value_str), + index_delta); +} + +std::optional<storage::VertexAccessor> DiskStorage::DiskAccessor::LoadVertexToLabelPropertyIndexCache( + const rocksdb::Slice &key, const rocksdb::Slice &value, Delta *index_delta, + utils::SkipList<storage::Vertex>::Accessor index_accessor) { + storage::Gid gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelPropertyIndexStorage(key.ToString()))); + if (VertexExistsInCache(index_accessor, gid)) { + return std::nullopt; + } + + const std::string value_str{value.ToString()}; + const auto labels{utils::DeserializeLabelsFromLabelPropertyIndexStorage(value_str)}; + return CreateVertex(index_accessor, gid, labels, + utils::DeserializePropertiesFromLabelPropertyIndexStorage(value.ToString()), index_delta); +} + +std::optional<EdgeAccessor> DiskStorage::DiskAccessor::DeserializeEdge(const rocksdb::Slice &key, + const rocksdb::Slice &value) { + const auto edge_parts = utils::Split(key.ToStringView(), "|"); + const Gid edge_gid = Gid::FromUint(std::stoull(edge_parts[4])); + + auto edge_acc = edges_.access(); + auto res = edge_acc.find(edge_gid); + if (res != edge_acc.end()) { + return std::nullopt; + } + + const auto [from_gid, to_gid] = std::invoke( + [](const auto &edge_parts) { + if (edge_parts[2] == "0") { // out edge + return std::make_pair(edge_parts[0], edge_parts[1]); + } + // in edge + return std::make_pair(edge_parts[1], edge_parts[0]); + }, + edge_parts); + + const auto from_acc = FindVertex(Gid::FromUint(std::stoull(from_gid)), View::OLD); + const auto to_acc = FindVertex(Gid::FromUint(std::stoull(to_gid)), View::OLD); + if (!from_acc || !to_acc) { + throw utils::BasicException("Non-existing vertices found during edge deserialization"); + } + const auto edge_type_id = storage::EdgeTypeId::FromUint(std::stoull(edge_parts[3])); + auto maybe_edge = CreateEdge(&*from_acc, &*to_acc, edge_type_id, edge_gid, value.ToStringView(), key.ToString()); + MG_ASSERT(maybe_edge.HasValue()); + + return *maybe_edge; +} + +VerticesIterable DiskStorage::DiskAccessor::Vertices(View view) { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = + std::unique_ptr<rocksdb::Iterator>(disk_transaction_->GetIterator(ro, disk_storage->kvstore_->vertex_chandle)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + LoadVertexToMainMemoryCache(it->key(), it->value()); + } + return VerticesIterable(AllVerticesIterable(vertices_.access(), &transaction_, view, &storage_->indices_, + &storage_->constraints_, storage_->config_.items)); +} + +VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, View view) { + index_storage_.emplace_back(std::make_unique<utils::SkipList<storage::Vertex>>()); + auto &indexed_vertices = index_storage_.back(); + index_deltas_storage_.emplace_back(std::list<Delta>()); + auto &index_deltas = index_deltas_storage_.back(); + + auto *disk_label_index = static_cast<DiskLabelIndex *>(storage_->indices_.label_index_.get()); + auto disk_index_transaction = disk_label_index->CreateRocksDBTransaction(); + disk_index_transaction->SetReadTimestampForValidation(transaction_.start_timestamp); + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto index_it = std::unique_ptr<rocksdb::Iterator>(disk_index_transaction->GetIterator(ro)); + + auto main_cache_acc = vertices_.access(); + std::unordered_set<storage::Gid> gids(main_cache_acc.size()); + for (const auto &vertex : main_cache_acc) { + gids.insert(vertex.gid); + if (VertexHasLabel(vertex, label, &transaction_, view)) { + spdlog::debug("Loaded vertex with gid: {} from main index storage to label index", + utils::SerializeIdType(vertex.gid)); + LoadVertexToLabelIndexCache(utils::SerializeVertexAsKeyForLabelIndex(label, vertex.gid), + utils::SerializeVertexAsValueForLabelIndex(label, vertex.labels, vertex.properties), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, std::nullopt), + indexed_vertices->access()); + } + } + + for (index_it->SeekToFirst(); index_it->Valid(); index_it->Next()) { + std::string key = index_it->key().ToString(); + Gid curr_gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelIndexStorage(key))); + spdlog::debug("Loaded vertex with key: {} from label index storage", key); + /// TODO: optimize + if (key.starts_with(utils::SerializeIdType(label)) && !utils::Contains(gids, curr_gid)) { + LoadVertexToLabelIndexCache(index_it->key(), index_it->value(), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, key), + indexed_vertices->access()); + } + } + + return VerticesIterable(AllVerticesIterable(indexed_vertices->access(), &transaction_, view, &storage_->indices_, + &storage_->constraints_, storage_->config_.items)); +} + +VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, PropertyId property, View view) { + index_storage_.emplace_back(std::make_unique<utils::SkipList<storage::Vertex>>()); + auto &indexed_vertices = index_storage_.back(); + index_deltas_storage_.emplace_back(std::list<Delta>()); + auto &index_deltas = index_deltas_storage_.back(); + + auto *disk_label_property_index = + static_cast<DiskLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + + auto disk_index_transaction = disk_label_property_index->CreateRocksDBTransaction(); + disk_index_transaction->SetReadTimestampForValidation(transaction_.start_timestamp); + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto index_it = std::unique_ptr<rocksdb::Iterator>(disk_index_transaction->GetIterator(ro)); + + auto main_cache_acc = vertices_.access(); + std::unordered_set<storage::Gid> gids(main_cache_acc.size()); + for (const auto &vertex : main_cache_acc) { + gids.insert(vertex.gid); + /// TODO: delta support for clearing old disk keys + if (VertexHasLabel(vertex, label, &transaction_, view) && + HasVertexProperty(vertex, property, &transaction_, view)) { + LoadVertexToLabelPropertyIndexCache( + utils::SerializeVertexAsKeyForLabelPropertyIndex(label, property, vertex.gid), + utils::SerializeVertexAsValueForLabelPropertyIndex(label, vertex.labels, vertex.properties), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, std::nullopt), + indexed_vertices->access()); + } + } + + for (index_it->SeekToFirst(); index_it->Valid(); index_it->Next()) { + std::string key = index_it->key().ToString(); + Gid curr_gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelPropertyIndexStorage(key))); + /// TODO: optimize + if (key.starts_with(utils::SerializeIdType(label) + "|" + utils::SerializeIdType(property)) && + !utils::Contains(gids, curr_gid)) { + LoadVertexToLabelPropertyIndexCache(index_it->key(), index_it->value(), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, key), + indexed_vertices->access()); + } + } + + return VerticesIterable(AllVerticesIterable(indexed_vertices->access(), &transaction_, view, &storage_->indices_, + &storage_->constraints_, storage_->config_.items)); +} + +VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, + View view) { + index_storage_.emplace_back(std::make_unique<utils::SkipList<storage::Vertex>>()); + auto &indexed_vertices = index_storage_.back(); + index_deltas_storage_.emplace_back(std::list<Delta>()); + auto &index_deltas = index_deltas_storage_.back(); + + auto *disk_label_property_index = + static_cast<DiskLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + + auto disk_index_transaction = disk_label_property_index->CreateRocksDBTransaction(); + disk_index_transaction->SetReadTimestampForValidation(transaction_.start_timestamp); + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto index_it = std::unique_ptr<rocksdb::Iterator>(disk_index_transaction->GetIterator(ro)); + + auto main_cache_acc = vertices_.access(); + std::unordered_set<storage::Gid> gids(main_cache_acc.size()); + for (const auto &vertex : main_cache_acc) { + gids.insert(vertex.gid); + if (VertexHasLabel(vertex, label, &transaction_, view) && + HasVertexEqualPropertyValue(vertex, property, value, &transaction_, view)) { + LoadVertexToLabelPropertyIndexCache( + utils::SerializeVertexAsKeyForLabelPropertyIndex(label, property, vertex.gid), + utils::SerializeVertexAsValueForLabelPropertyIndex(label, vertex.labels, vertex.properties), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, std::nullopt), + indexed_vertices->access()); + } + } + + for (index_it->SeekToFirst(); index_it->Valid(); index_it->Next()) { + std::string key_str = index_it->key().ToString(); + std::string it_value_str = index_it->value().ToString(); + Gid curr_gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelPropertyIndexStorage(key_str))); + /// TODO: optimize + /// TODO: couple this condition + PropertyStore properties = utils::DeserializePropertiesFromLabelPropertyIndexStorage(it_value_str); + if (key_str.starts_with(utils::SerializeIdType(label) + "|" + utils::SerializeIdType(property)) && + !utils::Contains(gids, curr_gid) && properties.IsPropertyEqual(property, value)) { + LoadVertexToLabelPropertyIndexCache( + index_it->key(), index_it->value(), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, key_str), indexed_vertices->access()); + } + } + + return VerticesIterable(AllVerticesIterable(indexed_vertices->access(), &transaction_, view, &storage_->indices_, + &storage_->constraints_, storage_->config_.items)); +} + +VerticesIterable DiskStorage::DiskAccessor::Vertices(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, + View view) { + index_storage_.emplace_back(std::make_unique<utils::SkipList<storage::Vertex>>()); + auto &indexed_vertices = index_storage_.back(); + index_deltas_storage_.emplace_back(std::list<Delta>()); + auto &index_deltas = index_deltas_storage_.back(); + + auto *disk_label_property_index = + static_cast<DiskLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + + auto disk_index_transaction = disk_label_property_index->CreateRocksDBTransaction(); + disk_index_transaction->SetReadTimestampForValidation(transaction_.start_timestamp); + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto index_it = std::unique_ptr<rocksdb::Iterator>(disk_index_transaction->GetIterator(ro)); + + auto main_cache_acc = vertices_.access(); + std::unordered_set<storage::Gid> gids(main_cache_acc.size()); + for (const auto &vertex : main_cache_acc) { + gids.insert(vertex.gid); + /// TODO: refactor in one method + auto prop_value = GetVertexProperty(vertex, property, &transaction_, view); + if (VertexHasLabel(vertex, label, &transaction_, view) && + IsPropertyValueWithinInterval(prop_value, lower_bound, upper_bound)) { + LoadVertexToLabelPropertyIndexCache( + utils::SerializeVertexAsKeyForLabelPropertyIndex(label, property, vertex.gid), + utils::SerializeVertexAsValueForLabelPropertyIndex(label, vertex.labels, vertex.properties), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, std::nullopt), + indexed_vertices->access()); + } + } + + for (index_it->SeekToFirst(); index_it->Valid(); index_it->Next()) { + std::string key_str = index_it->key().ToString(); + std::string it_value_str = index_it->value().ToString(); + Gid curr_gid = Gid::FromUint(std::stoull(utils::ExtractGidFromLabelPropertyIndexStorage(key_str))); + // TODO: andi this will be optimized bla bla + /// TODO: couple this condition + PropertyStore properties = utils::DeserializePropertiesFromLabelPropertyIndexStorage(it_value_str); + auto prop_value = properties.GetProperty(property); + if (key_str.starts_with(utils::SerializeIdType(label) + "|" + utils::SerializeIdType(property)) && + !utils::Contains(gids, curr_gid) && IsPropertyValueWithinInterval(prop_value, lower_bound, upper_bound)) { + LoadVertexToLabelPropertyIndexCache( + index_it->key(), index_it->value(), + CreateDeleteDeserializedIndexObjectDelta(&transaction_, index_deltas, key_str), indexed_vertices->access()); + } + } + + return VerticesIterable(AllVerticesIterable(indexed_vertices->access(), &transaction_, view, &storage_->indices_, + &storage_->constraints_, storage_->config_.items)); +} + +uint64_t DiskStorage::DiskAccessor::ApproximateVertexCount() const { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + return disk_storage->kvstore_->ApproximateVertexCount(); +} + +bool DiskStorage::PersistLabelIndexCreation(LabelId label) const { + if (auto label_index_store = durability_kvstore_->Get(label_index_str); label_index_store.has_value()) { + std::string &value = label_index_store.value(); + value += "|" + utils::SerializeIdType(label); + return durability_kvstore_->Put(label_index_str, value); + } + return durability_kvstore_->Put(label_index_str, utils::SerializeIdType(label)); +} + +bool DiskStorage::PersistLabelIndexDeletion(LabelId label) const { + if (auto label_index_store = durability_kvstore_->Get(label_index_str); label_index_store.has_value()) { + const std::string &value = label_index_store.value(); + std::vector<std::string> labels = utils::Split(value, "|"); + std::erase(labels, utils::SerializeIdType(label)); + if (labels.empty()) { + return durability_kvstore_->Delete(label_index_str); + } + return durability_kvstore_->Put(label_index_str, utils::Join(labels, "|")); + } + return true; +} + +bool DiskStorage::PersistLabelPropertyIndexAndExistenceConstraintCreation(LabelId label, PropertyId property, + const char *key) const { + if (auto label_property_index_store = durability_kvstore_->Get(key); label_property_index_store.has_value()) { + std::string &value = label_property_index_store.value(); + value += "|" + utils::SerializeIdType(label) + "," + utils::SerializeIdType(property); + return durability_kvstore_->Put(key, value); + } + return durability_kvstore_->Put(key, utils::SerializeIdType(label) + "," + utils::SerializeIdType(property)); +} + +bool DiskStorage::PersistLabelPropertyIndexAndExistenceConstraintDeletion(LabelId label, PropertyId property, + const char *key) const { + if (auto label_property_index_store = durability_kvstore_->Get(key); label_property_index_store.has_value()) { + const std::string &value = label_property_index_store.value(); + std::vector<std::string> label_properties = utils::Split(value, "|"); + std::erase(label_properties, utils::SerializeIdType(label) + "," + utils::SerializeIdType(property)); + if (label_properties.empty()) { + return durability_kvstore_->Delete(key); + } + return durability_kvstore_->Put(key, utils::Join(label_properties, "|")); + } + return true; +} + +bool DiskStorage::PersistUniqueConstraintCreation(LabelId label, const std::set<PropertyId> &properties) const { + std::string entry = utils::SerializeIdType(label); + for (auto property : properties) { + entry += "," + utils::SerializeIdType(property); + } + + if (auto unique_store = durability_kvstore_->Get(unique_constraints_str); unique_store.has_value()) { + std::string &value = unique_store.value(); + value += "|" + entry; + return durability_kvstore_->Put(unique_constraints_str, value); + } + return durability_kvstore_->Put(unique_constraints_str, entry); +} + +bool DiskStorage::PersistUniqueConstraintDeletion(LabelId label, const std::set<PropertyId> &properties) const { + std::string entry = utils::SerializeIdType(label); + for (auto property : properties) { + entry += "," + utils::SerializeIdType(property); + } + + if (auto unique_store = durability_kvstore_->Get(unique_constraints_str); unique_store.has_value()) { + const std::string &value = unique_store.value(); + std::vector<std::string> unique_constraints = utils::Split(value, "|"); + std::erase(unique_constraints, entry); + if (unique_constraints.empty()) { + return durability_kvstore_->Delete(unique_constraints_str); + } + return durability_kvstore_->Put(unique_constraints_str, utils::Join(unique_constraints, "|")); + } + return true; +} + +uint64_t DiskStorage::GetDiskSpaceUsage() const { + uint64_t main_disk_storage_size = utils::GetDirDiskUsage(config_.disk.main_storage_directory); + uint64_t index_disk_storage_size = utils::GetDirDiskUsage(config_.disk.label_index_directory) + + utils::GetDirDiskUsage(config_.disk.label_property_index_directory); + uint64_t constraints_disk_storage_size = utils::GetDirDiskUsage(config_.disk.unique_constraints_directory); + uint64_t metadata_disk_storage_size = utils::GetDirDiskUsage(config_.disk.id_name_mapper_directory) + + utils::GetDirDiskUsage(config_.disk.name_id_mapper_directory); + uint64_t durability_disk_storage_size = + utils::GetDirDiskUsage(config_.disk.durability_directory) + utils::GetDirDiskUsage(config_.disk.wal_directory); + return main_disk_storage_size + index_disk_storage_size + constraints_disk_storage_size + metadata_disk_storage_size + + durability_disk_storage_size; +} + +StorageInfo DiskStorage::GetInfo() const { + auto vertex_count = kvstore_->ApproximateVertexCount(); + auto edge_count = kvstore_->ApproximateEdgeCount(); + double average_degree = 0.0; + if (vertex_count) { + // NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + average_degree = 2.0 * static_cast<double>(edge_count) / vertex_count; + } + + return {vertex_count, edge_count, average_degree, utils::GetMemoryUsage(), GetDiskSpaceUsage()}; +} + +VertexAccessor DiskStorage::DiskAccessor::CreateVertex() { + auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel); + auto acc = vertices_.access(); + + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta}); + MG_ASSERT(inserted, "The vertex must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); + + if (delta) { + delta->prev.Set(&*it); + } + + return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; +} + +VertexAccessor DiskStorage::DiskAccessor::CreateVertex(utils::SkipList<Vertex>::Accessor &accessor, storage::Gid gid, + const std::vector<LabelId> &label_ids, + PropertyStore &&properties, Delta *delta) { + OOMExceptionEnabler oom_exception; + auto *disk_storage = static_cast<DiskStorage *>(storage_); + disk_storage->vertex_id_.store(std::max(disk_storage->vertex_id_.load(std::memory_order_acquire), gid.AsUint() + 1), + std::memory_order_release); + auto [it, inserted] = accessor.insert(Vertex{gid, delta}); + MG_ASSERT(inserted, "The vertex must be inserted here!"); + MG_ASSERT(it != accessor.end(), "Invalid Vertex accessor!"); + /// TODO: move + for (auto label_id : label_ids) { + it->labels.push_back(label_id); + } + it->properties = std::move(properties); + if (delta) { + delta->prev.Set(&*it); + } + return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; +} + +std::optional<VertexAccessor> DiskStorage::DiskAccessor::FindVertex(storage::Gid gid, View view) { + auto acc = vertices_.access(); + auto vertex_it = acc.find(gid); + if (vertex_it != acc.end()) { + return VertexAccessor::Create(&*vertex_it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, + view); + } + for (const auto &vec : index_storage_) { + acc = vec->access(); + auto index_it = acc.find(gid); + if (index_it != acc.end()) { + return VertexAccessor::Create(&*index_it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, + view); + } + } + + rocksdb::ReadOptions read_opts; + auto strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + read_opts.timestamp = &ts; + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto it = std::unique_ptr<rocksdb::Iterator>( + disk_transaction_->GetIterator(read_opts, disk_storage->kvstore_->vertex_chandle)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const auto &key = it->key(); + if (Gid::FromUint(std::stoull(utils::ExtractGidFromKey(key.ToString()))) == gid) { + return LoadVertexToMainMemoryCache(key, it->value()); + } + } + return std::nullopt; +} + +Result<std::optional<VertexAccessor>> DiskStorage::DiskAccessor::DeleteVertex(VertexAccessor *vertex) { + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) { + return std::optional<VertexAccessor>{}; + } + + if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) return Error::VERTEX_HAS_EDGES; + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + vertices_to_delete_.emplace_back(utils::SerializeIdType(vertex_ptr->gid), utils::SerializeVertex(*vertex_ptr)); + + return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, + config_, true); +} + +Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> +DiskStorage::DiskAccessor::DetachDeleteVertex(VertexAccessor *vertex) { + using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>; + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; + std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; + + { + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) return std::optional<ReturnType>{}; + + in_edges = vertex_ptr->in_edges; + out_edges = vertex_ptr->out_edges; + } + + std::vector<EdgeAccessor> deleted_edges; + for (const auto &item : in_edges) { + auto [edge_type, from_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + for (const auto &item : out_edges) { + auto [edge_type, to_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, + config_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + // We need to check again for serialization errors because we unlocked the + // vertex. Some other transaction could have modified the vertex in the + // meantime if we didn't have any edges to delete. + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + vertices_to_delete_.emplace_back(utils::SerializeIdType(vertex_ptr->gid), utils::SerializeVertex(*vertex_ptr)); + + return std::make_optional<ReturnType>( + VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true}, + std::move(deleted_edges)); +} + +void DiskStorage::DiskAccessor::PrefetchEdges(const auto &prefetch_edge_filter) { + rocksdb::ReadOptions read_opts; + auto strTs = utils::StringTimestamp(transaction_.start_timestamp); + rocksdb::Slice ts(strTs); + read_opts.timestamp = &ts; + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto it = std::unique_ptr<rocksdb::Iterator>( + disk_transaction_->GetIterator(read_opts, disk_storage->kvstore_->edge_chandle)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const rocksdb::Slice &key = it->key(); + const auto edge_parts = utils::Split(key.ToStringView(), "|"); + if (prefetch_edge_filter(edge_parts)) { + DeserializeEdge(key, it->value()); + } + } +} + +void DiskStorage::DiskAccessor::PrefetchInEdges(const VertexAccessor &vertex_acc) { + PrefetchEdges([&vertex_acc](const std::vector<std::string> &disk_edge_parts) -> bool { + auto disk_vertex_in_edge_gid = disk_edge_parts[1]; + auto edge_gid = disk_edge_parts[4]; + auto in_edges_res = vertex_acc.InEdges(storage::View::NEW); + if (in_edges_res.HasValue()) { + for (const auto &edge_acc : in_edges_res.GetValue()) { + if (utils::SerializeIdType(edge_acc.Gid()) == edge_gid) { + // We already inserted this edge into the vertex's in_edges list. + return false; + } + } + } + return disk_vertex_in_edge_gid == utils::SerializeIdType(vertex_acc.Gid()); + }); +} + +void DiskStorage::DiskAccessor::PrefetchOutEdges(const VertexAccessor &vertex_acc) { + PrefetchEdges([&vertex_acc](const std::vector<std::string> &disk_edge_parts) -> bool { + auto disk_vertex_out_edge_gid = disk_edge_parts[0]; + auto edge_gid = disk_edge_parts[4]; + auto out_edges_res = vertex_acc.OutEdges(storage::View::NEW); + if (out_edges_res.HasValue()) { + for (const auto &edge_acc : out_edges_res.GetValue()) { + if (utils::SerializeIdType(edge_acc.Gid()) == edge_gid) { + // We already inserted this edge into the vertex's out_edges list. + return false; + } + } + } + return disk_vertex_out_edge_gid == utils::SerializeIdType(vertex_acc.Gid()); + }); +} + +Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(const VertexAccessor *from, const VertexAccessor *to, + EdgeTypeId edge_type, storage::Gid gid, + const std::string_view properties, + const std::string &old_disk_key) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(from->transaction_ == to->transaction_, + "VertexAccessors must be from the same transaction when creating " + "an edge!"); + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessors must be from the same transaction in when " + "creating an edge!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + auto *disk_storage = static_cast<DiskStorage *>(storage_); + disk_storage->edge_id_.store(std::max(disk_storage->edge_id_.load(std::memory_order_acquire), gid.AsUint() + 1), + std::memory_order_release); + + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = edges_.access(); + auto *delta = CreateDeleteDeserializedObjectDelta(&transaction_, old_disk_key); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + if (delta) { + delta->prev.Set(&*it); + } + edge.ptr->properties.SetBuffer(properties); + } + + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); +} + +Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, + EdgeTypeId edge_type) { + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + MG_ASSERT(to->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto gid = storage::Gid::FromUint(disk_storage->edge_id_.fetch_add(1, std::memory_order_acq_rel)); + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = edges_.access(); + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + delta->prev.Set(&*it); + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + // Increment edge count. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); +} + +Result<std::optional<EdgeAccessor>> DiskStorage::DiskAccessor::DeleteEdge(EdgeAccessor *edge) { + MG_ASSERT(edge->transaction_ == &transaction_, + "EdgeAccessor must be from the same transaction as the storage " + "accessor when deleting an edge!"); + const auto edge_ref = edge->edge_; + const auto edge_type = edge->edge_type_; + + std::unique_lock<utils::SpinLock> guard; + if (config_.properties_on_edges) { + const auto *edge_ptr = edge_ref.ptr; + guard = std::unique_lock<utils::SpinLock>(edge_ptr->lock); + + if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; + + if (edge_ptr->deleted) return std::optional<EdgeAccessor>{}; + } + + auto *from_vertex = edge->from_vertex_; + auto *to_vertex = edge->to_vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + } + + auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { + const std::tuple<EdgeTypeId, Vertex *, EdgeRef> link(edge_type, vertex, edge_ref); + auto it = std::find(edges->begin(), edges->end(), link); + if (config_.properties_on_edges) { + MG_ASSERT(it != edges->end(), "Invalid database state!"); + } else if (it == edges->end()) { + return false; + } + std::swap(*it, *edges->rbegin()); + edges->pop_back(); + return true; + }; + + const auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); + const auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); + + const std::string src_dest_del_key{ + utils::SerializeEdge(from_vertex->gid, to_vertex->gid, edge_type, edge_ref, config_.properties_on_edges)}; + edges_to_delete_.emplace_back(src_dest_del_key); + + if (config_.properties_on_edges) { + MG_ASSERT((op1 && op2), "Invalid database state!"); + } else { + MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); + if (!op1 && !op2) { + // The edge is already deleted. + return std::optional<EdgeAccessor>{}; + } + } + + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag()); + edge_ptr->deleted = true; + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + + // Decrement edge count. + storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); + + return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, + &storage_->indices_, &storage_->constraints_, config_, true); +} + +/// TODO: at which storage naming +/// TODO: this method should also delete the old key +bool DiskStorage::DiskAccessor::WriteVertexToDisk(const Vertex &vertex) { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto status = disk_transaction_->Put(disk_storage->kvstore_->vertex_chandle, utils::SerializeVertex(vertex), + utils::SerializeProperties(vertex.properties)); + if (status.ok()) { + spdlog::debug("rocksdb: Saved vertex with key {} and ts {}", utils::SerializeVertex(vertex), *commit_timestamp_); + } else if (status.IsBusy()) { + spdlog::error("rocksdb: Vertex with key {} and ts {} was changed and committed in another transaction", + utils::SerializeVertex(vertex), *commit_timestamp_); + return false; + } else { + spdlog::error("rocksdb: Failed to save vertex with key {} and ts {}", utils::SerializeVertex(vertex), + *commit_timestamp_); + return false; + } + return true; +} + +/// TODO: at which storage naming +bool DiskStorage::DiskAccessor::WriteEdgeToDisk(const EdgeRef edge, const std::string &serializedEdgeKey) { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + rocksdb::Status status; + if (config_.properties_on_edges) { + status = disk_transaction_->Put(disk_storage->kvstore_->edge_chandle, serializedEdgeKey, + utils::SerializeProperties(edge.ptr->properties)); + } else { + status = disk_transaction_->Put(disk_storage->kvstore_->edge_chandle, serializedEdgeKey, ""); + } + if (status.ok()) { + spdlog::debug("rocksdb: Saved edge with key {} and ts {}", serializedEdgeKey, *commit_timestamp_); + } else if (status.IsBusy()) { + spdlog::error("rocksdb: Edge with key {} and ts {} was changed and committed in another transaction", + serializedEdgeKey, *commit_timestamp_); + return false; + } else { + spdlog::error("rocksdb: Failed to save edge with key {} and ts {}", serializedEdgeKey, *commit_timestamp_); + return false; + } + return true; +} + +bool DiskStorage::DiskAccessor::DeleteVertexFromDisk(const std::string &vertex) { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto status = disk_transaction_->Delete(disk_storage->kvstore_->vertex_chandle, vertex); + if (status.ok()) { + spdlog::debug("rocksdb: Deleted vertex with key {}", vertex); + } else if (status.IsBusy()) { + spdlog::error("rocksdb: Vertex with key {} was changed and committed in another transaction", vertex); + return false; + } else { + spdlog::error("rocksdb: Failed to delete vertex with key {}", vertex); + return false; + } + return true; +} + +bool DiskStorage::DiskAccessor::DeleteEdgeFromDisk(const std::string &edge) { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + auto status = disk_transaction_->Delete(disk_storage->kvstore_->edge_chandle, edge); + if (status.ok()) { + spdlog::debug("rocksdb: Deleted edge with key {}", edge); + } else if (status.IsBusy()) { + spdlog::error("rocksdb: Edge with key {} was changed and committed in another transaction", edge); + return false; + } else { + spdlog::error("rocksdb: Failed to delete edge with key {}", edge); + return false; + } + return true; +} + +[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> +DiskStorage::DiskAccessor::CheckVertexConstraintsBeforeCommit( + const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage) const { + if (auto existence_constraint_validation_result = storage_->constraints_.existence_constraints_->Validate(vertex); + existence_constraint_validation_result.has_value()) { + return StorageDataManipulationError{existence_constraint_validation_result.value()}; + } + + auto *disk_unique_constraints = + static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get()); + if (auto unique_constraint_validation_result = disk_unique_constraints->Validate(vertex, unique_storage); + unique_constraint_validation_result.has_value()) { + return StorageDataManipulationError{unique_constraint_validation_result.value()}; + } + return {}; +} + +[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushMainMemoryCache() { + auto vertex_acc = vertices_.access(); + + std::vector<std::vector<PropertyValue>> unique_storage; + auto *disk_unique_constraints = + static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get()); + auto *disk_label_index = static_cast<DiskLabelIndex *>(storage_->indices_.label_index_.get()); + auto *disk_label_property_index = + static_cast<DiskLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + + /// TODO: andi I don't like that std::optional is used for checking errors but that's how it was before, refactor! + for (Vertex &vertex : vertex_acc) { + if (auto check_result = CheckVertexConstraintsBeforeCommit(vertex, unique_storage); check_result.HasError()) { + return check_result.GetError(); + } + + /// TODO: what if something is changed and then deleted + if (vertex.deleted) { + continue; + } + + /// TODO: expose temporal coupling + /// NOTE: this deletion has to come before writing, otherwise RocksDB thinks that all entries are deleted + /// TODO: This has to deal with index storage if read from index cache + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (!DeleteVertexFromDisk(maybe_old_disk_key.value())) { + return StorageDataManipulationError{SerializationError{}}; + } + } + + if (!WriteVertexToDisk(vertex)) { + return StorageDataManipulationError{SerializationError{}}; + } + + /// TODO: andi don't ignore the return value + if (!disk_unique_constraints->SyncVertexToUniqueConstraintsStorage(vertex, *commit_timestamp_) || + !disk_label_index->SyncVertexToLabelIndexStorage(vertex, *commit_timestamp_) || + !disk_label_property_index->SyncVertexToLabelPropertyIndexStorage(vertex, *commit_timestamp_)) { + return StorageDataManipulationError{SerializationError{}}; + } + + for (auto &edge_entry : vertex.out_edges) { + EdgeRef edge = std::get<2>(edge_entry); + auto src_dest_key = utils::SerializeEdge(vertex.gid, std::get<1>(edge_entry)->gid, std::get<0>(edge_entry), edge, + config_.properties_on_edges); + + /// TODO: expose temporal coupling + /// NOTE: this deletion has to come before writing, otherwise RocksDB thinks that all entries are deleted + if (config_.properties_on_edges) { + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(edge.ptr->delta); maybe_old_disk_key.has_value()) { + if (!DeleteEdgeFromDisk(maybe_old_disk_key.value())) { + return StorageDataManipulationError{SerializationError{}}; + } + } + } + + if (!WriteEdgeToDisk(edge, src_dest_key)) { + return StorageDataManipulationError{SerializationError{}}; + } + + /// TODO: what if edge has already been deleted + } + } + + for (const auto &[vertex_gid, serialized_vertex_to_delete] : vertices_to_delete_) { + if (!DeleteVertexFromDisk(serialized_vertex_to_delete) || + !disk_unique_constraints->ClearDeletedVertex(vertex_gid, *commit_timestamp_) || + !disk_label_index->ClearDeletedVertex(vertex_gid, *commit_timestamp_) || + !disk_label_property_index->ClearDeletedVertex(vertex_gid, *commit_timestamp_)) { + return StorageDataManipulationError{SerializationError{}}; + } + } + + for (const auto &edge_to_delete : edges_to_delete_) { + if (!DeleteEdgeFromDisk(edge_to_delete)) { + return StorageDataManipulationError{SerializationError{}}; + } + } + + if (!disk_unique_constraints->DeleteVerticesWithRemovedConstraintLabel(transaction_.start_timestamp, + *commit_timestamp_) || + !disk_label_index->DeleteVerticesWithRemovedIndexingLabel(transaction_.start_timestamp, *commit_timestamp_) || + !disk_label_property_index->DeleteVerticesWithRemovedIndexingLabel(transaction_.start_timestamp, + *commit_timestamp_)) { + return StorageDataManipulationError{SerializationError{}}; + } + + return {}; +} + +/// TODO: I think unique_storage is not needed here +[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushIndexCache() { + std::vector<std::vector<PropertyValue>> unique_storage; + auto *disk_unique_constraints = + static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get()); + auto *disk_label_index = static_cast<DiskLabelIndex *>(storage_->indices_.label_index_.get()); + auto *disk_label_property_index = + static_cast<DiskLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + + for (const auto &vec : index_storage_) { + auto vertex_acc = vec->access(); + for (Vertex &vertex : vertex_acc) { + if (auto check_result = CheckVertexConstraintsBeforeCommit(vertex, unique_storage); check_result.HasError()) { + return check_result.GetError(); + } + + /// TODO: what if something is changed and then deleted + if (vertex.deleted) { + continue; + } + + if (!WriteVertexToDisk(vertex)) { + return StorageDataManipulationError{SerializationError{}}; + } + + /// TODO: andi don't ignore the return value + if (!disk_unique_constraints->SyncVertexToUniqueConstraintsStorage(vertex, *commit_timestamp_) || + !disk_label_index->SyncVertexToLabelIndexStorage(vertex, *commit_timestamp_) || + !disk_label_property_index->SyncVertexToLabelPropertyIndexStorage(vertex, *commit_timestamp_)) { + return StorageDataManipulationError{SerializationError{}}; + } + + for (auto &edge_entry : vertex.out_edges) { + EdgeRef edge = std::get<2>(edge_entry); + auto src_dest_key = utils::SerializeEdge(vertex.gid, std::get<1>(edge_entry)->gid, std::get<0>(edge_entry), + edge, config_.properties_on_edges); + + if (!WriteEdgeToDisk(edge, src_dest_key)) { + return StorageDataManipulationError{SerializationError{}}; + } + + /// TODO: what if edge has already been deleted + } + } + } + + return {}; +} + +[[nodiscard]] std::optional<ConstraintViolation> DiskStorage::CheckExistingVerticesBeforeCreatingExistenceConstraint( + LabelId label, PropertyId property) const { + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(kvstore_->db_->NewIterator(ro, kvstore_->vertex_chandle)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + std::vector<LabelId> labels = utils::DeserializeLabelsFromMainDiskStorage(it->key().ToString()); + PropertyStore properties = utils::DeserializePropertiesFromMainDiskStorage(it->value().ToStringView()); + if (utils::Contains(labels, label) && !properties.HasProperty(property)) { + return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}}; + } + } + return std::nullopt; +} + +[[nodiscard]] utils::BasicResult<ConstraintViolation, std::vector<std::pair<std::string, std::string>>> +DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label, + const std::set<PropertyId> &properties) const { + std::set<std::vector<PropertyValue>> unique_storage; + std::vector<std::pair<std::string, std::string>> vertices_for_constraints; + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(kvstore_->db_->NewIterator(ro, kvstore_->vertex_chandle)); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const std::string key_str = it->key().ToString(); + std::vector<LabelId> labels = utils::DeserializeLabelsFromMainDiskStorage(key_str); + PropertyStore property_store = utils::DeserializePropertiesFromMainDiskStorage(it->value().ToStringView()); + if (utils::Contains(labels, label) && property_store.HasAllProperties(properties)) { + if (auto target_property_values = property_store.ExtractPropertyValues(properties); + target_property_values.has_value() && !utils::Contains(unique_storage, *target_property_values)) { + unique_storage.insert(*target_property_values); + vertices_for_constraints.emplace_back( + utils::SerializeVertexAsKeyForUniqueConstraint(label, properties, utils::ExtractGidFromKey(key_str)), + utils::SerializeVertexAsValueForUniqueConstraint(label, labels, property_store)); + } else { + return ConstraintViolation{ConstraintViolation::Type::UNIQUE, label, properties}; + } + } + } + return vertices_for_constraints; +} + +// NOLINTNEXTLINE(google-default-arguments) +utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::Commit( + const std::optional<uint64_t> desired_commit_timestamp) { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); + + auto *disk_storage = static_cast<DiskStorage *>(storage_); + + if (transaction_.deltas.empty() || + std::all_of(transaction_.deltas.begin(), transaction_.deltas.end(), + [](const Delta &delta) { return delta.action == Delta::Action::DELETE_DESERIALIZED_OBJECT; })) { + } else { + std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); + commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp)); + + if (auto res = FlushMainMemoryCache(); res.HasError()) { + Abort(); + return res; + } + + if (auto res = FlushIndexCache(); res.HasError()) { + Abort(); + return res; + } + } + + if (commit_timestamp_) { + // commit_timestamp_ is set only if the transaction has writes. + logging::AssertRocksDBStatus(disk_transaction_->SetCommitTimestamp(*commit_timestamp_)); + } + auto commitStatus = disk_transaction_->Commit(); + delete disk_transaction_; + disk_transaction_ = nullptr; + if (!commitStatus.ok()) { + spdlog::error("rocksdb: Commit failed with status {}", commitStatus.ToString()); + return StorageDataManipulationError{SerializationError{}}; + } + spdlog::debug("rocksdb: Commit successful"); + + is_transaction_active_ = false; + + return {}; +} + +std::vector<std::pair<std::string, std::string>> DiskStorage::SerializeVerticesForLabelIndex(LabelId label) { + std::vector<std::pair<std::string, std::string>> vertices_to_be_indexed; + + rocksdb::ReadOptions ro; + auto strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(kvstore_->db_->NewIterator(ro, kvstore_->vertex_chandle)); + + const std::string serialized_label = utils::SerializeIdType(label); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const std::string key_str = it->key().ToString(); + if (const std::vector<std::string> labels_str = utils::ExtractLabelsFromMainDiskStorage(key_str); + utils::Contains(labels_str, serialized_label)) { + std::vector<LabelId> labels = utils::DeserializeLabelsFromMainDiskStorage(key_str); + PropertyStore property_store = utils::DeserializePropertiesFromMainDiskStorage(it->value().ToStringView()); + vertices_to_be_indexed.emplace_back( + utils::SerializeVertexAsKeyForLabelIndex(utils::SerializeIdType(label), + utils::ExtractGidFromMainDiskStorage(key_str)), + utils::SerializeVertexAsValueForLabelIndex(label, labels, property_store)); + } + } + return vertices_to_be_indexed; +} + +std::vector<std::pair<std::string, std::string>> DiskStorage::SerializeVerticesForLabelPropertyIndex( + LabelId label, PropertyId property) { + std::vector<std::pair<std::string, std::string>> vertices_to_be_indexed; + + rocksdb::ReadOptions ro; + auto strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(kvstore_->db_->NewIterator(ro, kvstore_->vertex_chandle)); + + const std::string serialized_label = utils::SerializeIdType(label); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + const std::string key_str = it->key().ToString(); + PropertyStore property_store = utils::DeserializePropertiesFromMainDiskStorage(it->value().ToString()); + if (const std::vector<std::string> labels_str = utils::ExtractLabelsFromMainDiskStorage(key_str); + utils::Contains(labels_str, serialized_label) && property_store.HasProperty(property)) { + std::vector<LabelId> labels = utils::DeserializeLabelsFromMainDiskStorage(key_str); + vertices_to_be_indexed.emplace_back( + utils::SerializeVertexAsKeyForLabelPropertyIndex(utils::SerializeIdType(label), + utils::SerializeIdType(property), + utils::ExtractGidFromMainDiskStorage(key_str)), + utils::SerializeVertexAsValueForLabelPropertyIndex(label, labels, property_store)); + } + } + return vertices_to_be_indexed; +} + +/// TODO: what to do with all that? +void DiskStorage::DiskAccessor::Abort() { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + // NOTE: On abort we need to delete disk transaction because after storage remove we couldn't remove + // disk_transaction correctly in destructor. + // This happens in tests when we create and remove storage in one test. For example, in + // query_plan_accumulate_aggregate.cpp + disk_transaction_->Rollback(); + disk_transaction_->ClearSnapshot(); + delete disk_transaction_; + disk_transaction_ = nullptr; + + is_transaction_active_ = false; +} + +void DiskStorage::DiskAccessor::FinalizeTransaction() { + if (commit_timestamp_) { + commit_timestamp_.reset(); + } +} + +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::CreateIndex( + LabelId label, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + auto *disk_label_index = static_cast<DiskLabelIndex *>(indices_.label_index_.get()); + if (!disk_label_index->CreateIndex(label, SerializeVerticesForLabelIndex(label))) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + + if (!PersistLabelIndexCreation(label)) { + return StorageIndexDefinitionError{IndexPersistenceError{}}; + } + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelIndices); + + return {}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::CreateIndex( + LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + auto *disk_label_property_index = static_cast<DiskLabelPropertyIndex *>(indices_.label_property_index_.get()); + if (!disk_label_property_index->CreateIndex(label, property, + SerializeVerticesForLabelPropertyIndex(label, property))) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + + if (!PersistLabelPropertyIndexAndExistenceConstraintCreation(label, property, label_property_index_str)) { + return StorageIndexDefinitionError{IndexPersistenceError{}}; + } + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); + + return {}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DropIndex( + LabelId label, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + if (!indices_.label_index_->DropIndex(label)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + + if (!PersistLabelIndexDeletion(label)) { + return StorageIndexDefinitionError{IndexPersistenceError{}}; + } + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelIndices); + + return {}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DropIndex( + LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + if (!indices_.label_property_index_->DropIndex(label, property)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + + if (!PersistLabelPropertyIndexAndExistenceConstraintDeletion(label, property, label_property_index_str)) { + return StorageIndexDefinitionError{IndexPersistenceError{}}; + } + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); + + return {}; +} + +utils::BasicResult<StorageExistenceConstraintDefinitionError, void> DiskStorage::CreateExistenceConstraint( + LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + if (constraints_.existence_constraints_->ConstraintExists(label, property)) { + return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}}; + } + + if (auto check = CheckExistingVerticesBeforeCreatingExistenceConstraint(label, property); check.has_value()) { + return StorageExistenceConstraintDefinitionError{check.value()}; + } + + constraints_.existence_constraints_->InsertConstraint(label, property); + + if (!PersistLabelPropertyIndexAndExistenceConstraintCreation(label, property, existence_constraints_str)) { + return StorageExistenceConstraintDefinitionError{ConstraintsPersistenceError{}}; + } + + return {}; +} + +utils::BasicResult<StorageExistenceConstraintDroppingError, void> DiskStorage::DropExistenceConstraint( + LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) { + if (!constraints_.existence_constraints_->DropConstraint(label, property)) { + return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}}; + } + + if (!PersistLabelPropertyIndexAndExistenceConstraintDeletion(label, property, existence_constraints_str)) { + return StorageExistenceConstraintDroppingError{ConstraintsPersistenceError{}}; + } + + return {}; +} + +utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> +DiskStorage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + auto *disk_unique_constraints = static_cast<DiskUniqueConstraints *>(constraints_.unique_constraints_.get()); + + if (auto constraint_check = disk_unique_constraints->CheckIfConstraintCanBeCreated(label, properties); + constraint_check != UniqueConstraints::CreationStatus::SUCCESS) { + return constraint_check; + } + + auto check = CheckExistingVerticesBeforeCreatingUniqueConstraint(label, properties); + if (check.HasError()) { + return StorageUniqueConstraintDefinitionError{check.GetError()}; + } + + if (!disk_unique_constraints->InsertConstraint(label, properties, check.GetValue())) { + return StorageUniqueConstraintDefinitionError{ConstraintDefinitionError{}}; + } + + if (!PersistUniqueConstraintCreation(label, properties)) { + return StorageUniqueConstraintDefinitionError{ConstraintsPersistenceError{}}; + } + + return UniqueConstraints::CreationStatus::SUCCESS; +} + +utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> +DiskStorage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + const std::optional<uint64_t> /*desired_commit_timestamp*/) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + auto ret = constraints_.unique_constraints_->DropConstraint(label, properties); + if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { + return ret; + } + + if (!PersistUniqueConstraintDeletion(label, properties)) { + return StorageUniqueConstraintDroppingError{ConstraintsPersistenceError{}}; + } + + return UniqueConstraints::DeletionStatus::SUCCESS; +} + +Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) { + /// We acquire the transaction engine lock here because we access (and + /// modify) the transaction engine variables (`transaction_id` and + /// `timestamp`) below. + uint64_t transaction_id = 0; + uint64_t start_timestamp = 0; + { + std::lock_guard<utils::SpinLock> guard(engine_lock_); + transaction_id = transaction_id_++; + /// TODO: when we introduce replication to the disk storage, take care of start_timestamp + start_timestamp = timestamp_++; + } + return {transaction_id, start_timestamp, isolation_level, storage_mode}; +} + +uint64_t DiskStorage::CommitTimestamp(const std::optional<uint64_t> desired_commit_timestamp) { + if (!desired_commit_timestamp) { + return timestamp_++; + } + timestamp_ = std::max(timestamp_, *desired_commit_timestamp + 1); + return *desired_commit_timestamp; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp new file mode 100644 index 000000000..f05dc00a7 --- /dev/null +++ b/src/storage/v2/disk/storage.hpp @@ -0,0 +1,303 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "kvstore/kvstore.hpp" +#include "storage/v2/constraints/constraint_violation.hpp" +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/isolation_level.hpp" +#include "storage/v2/property_store.hpp" +#include "storage/v2/storage.hpp" +#include "utils/rw_lock.hpp" + +#include <rocksdb/db.h> + +namespace memgraph::storage { + +class DiskStorage final : public Storage { + public: + explicit DiskStorage(Config config = Config()); + + DiskStorage(const DiskStorage &) = delete; + DiskStorage(DiskStorage &&) = delete; + DiskStorage &operator=(const DiskStorage &) = delete; + DiskStorage &operator=(DiskStorage &&) = delete; + + ~DiskStorage() override; + + class DiskAccessor final : public Storage::Accessor { + private: + friend class DiskStorage; + + explicit DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode); + + public: + DiskAccessor(const DiskAccessor &) = delete; + DiskAccessor &operator=(const DiskAccessor &) = delete; + DiskAccessor &operator=(DiskAccessor &&other) = delete; + + DiskAccessor(DiskAccessor &&other) noexcept; + + ~DiskAccessor() override; + + VertexAccessor CreateVertex() override; + + std::optional<VertexAccessor> FindVertex(Gid gid, View view) override; + + VerticesIterable Vertices(View view) override; + + VerticesIterable Vertices(LabelId label, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) override; + + uint64_t ApproximateVertexCount() const override; + + uint64_t ApproximateVertexCount(LabelId /*label*/) const override { return 10; } + + uint64_t ApproximateVertexCount(LabelId /*label*/, PropertyId /*property*/) const override { return 10; } + + uint64_t ApproximateVertexCount(LabelId /*label*/, PropertyId /*property*/, + const PropertyValue & /*value*/) const override { + return 10; + } + + uint64_t ApproximateVertexCount(LabelId /*label*/, PropertyId /*property*/, + const std::optional<utils::Bound<PropertyValue>> & /*lower*/, + const std::optional<utils::Bound<PropertyValue>> & /*upper*/) const override { + return 10; + } + + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId & /*label*/) const override { + return {}; + } + + std::optional<storage::LabelPropertyIndexStats> GetIndexStats( + const storage::LabelId & /*label*/, const storage::PropertyId & /*property*/) const override { + return {}; + } + + std::vector<LabelId> ClearLabelIndexStats() override { + throw utils::NotYetImplemented("ClearIndexStats() is not implemented for DiskStorage."); + } + + std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() override { + throw utils::NotYetImplemented("ClearIndexStats() is not implemented for DiskStorage."); + } + + std::vector<LabelId> DeleteLabelIndexStats(std::span<std::string> /*labels*/) override { + throw utils::NotYetImplemented("DeleteIndexStatsForLabels(labels) is not implemented for DiskStorage."); + } + + std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats( + const std::span<std::string> /*labels*/) override { + throw utils::NotYetImplemented("DeleteIndexStatsForLabels(labels) is not implemented for DiskStorage."); + } + + void SetIndexStats(const storage::LabelId & /*label*/, const LabelIndexStats & /*stats*/) override { + throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage."); + } + + void SetIndexStats(const storage::LabelId & /*label*/, const storage::PropertyId & /*property*/, + const LabelPropertyIndexStats & /*stats*/) override { + throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage."); + } + + /// TODO: It is just marked as deleted but the memory isn't reclaimed because of the in-memory storage + Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex) override; + + Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex( + VertexAccessor *vertex) override; + + void PrefetchInEdges(const VertexAccessor &vertex_acc) override; + + void PrefetchOutEdges(const VertexAccessor &vertex_acc) override; + + Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) override; + + Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge) override; + + bool LabelIndexExists(LabelId label) const override { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + return disk_storage->indices_.label_index_->IndexExists(label); + } + + bool LabelPropertyIndexExists(LabelId label, PropertyId property) const override { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + return disk_storage->indices_.label_property_index_->IndexExists(label, property); + } + + IndicesInfo ListAllIndices() const override { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + return disk_storage->ListAllIndices(); + } + + ConstraintsInfo ListAllConstraints() const override { + auto *disk_storage = static_cast<DiskStorage *>(storage_); + return disk_storage->ListAllConstraints(); + } + + // NOLINTNEXTLINE(google-default-arguments) + utils::BasicResult<StorageDataManipulationError, void> Commit( + std::optional<uint64_t> desired_commit_timestamp = {}) override; + + void Abort() override; + + void FinalizeTransaction() override; + + std::optional<storage::VertexAccessor> LoadVertexToLabelIndexCache( + const rocksdb::Slice &key, const rocksdb::Slice &value, Delta *index_delta, + utils::SkipList<storage::Vertex>::Accessor index_accessor); + + std::optional<storage::VertexAccessor> LoadVertexToMainMemoryCache(const rocksdb::Slice &key, + const rocksdb::Slice &value); + + std::optional<storage::VertexAccessor> LoadVertexToLabelPropertyIndexCache( + const rocksdb::Slice &key, const rocksdb::Slice &value, Delta *index_delta, + utils::SkipList<storage::Vertex>::Accessor index_accessor); + + std::optional<storage::EdgeAccessor> DeserializeEdge(const rocksdb::Slice &key, const rocksdb::Slice &value); + + private: + VertexAccessor CreateVertex(utils::SkipList<Vertex>::Accessor &accessor, storage::Gid gid, + const std::vector<LabelId> &label_ids, PropertyStore &&properties, Delta *delta); + + void PrefetchEdges(const auto &prefetch_edge_filter); + + Result<EdgeAccessor> CreateEdge(const VertexAccessor *from, const VertexAccessor *to, EdgeTypeId edge_type, + storage::Gid gid, std::string_view properties, const std::string &old_disk_key); + + /// Flushes vertices and edges to the disk with the commit timestamp. + /// At the time of calling, the commit_timestamp_ must already exist. + /// After this method, the vertex and edge caches are cleared. + [[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushMainMemoryCache(); + + [[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushIndexCache(); + + [[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> CheckVertexConstraintsBeforeCommit( + const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage) const; + + bool WriteVertexToDisk(const Vertex &vertex); + bool WriteEdgeToDisk(EdgeRef edge, const std::string &serializedEdgeKey); + bool DeleteVertexFromDisk(const std::string &vertex); + bool DeleteEdgeFromDisk(const std::string &edge); + + /// Main storage + utils::SkipList<storage::Vertex> vertices_; + std::vector<std::unique_ptr<utils::SkipList<storage::Vertex>>> index_storage_; + + /// We need them because query context for indexed reading is cleared after the query is done not after the + /// transaction is done + std::vector<std::list<Delta>> index_deltas_storage_; + utils::SkipList<storage::Edge> edges_; + Config::Items config_; + std::vector<std::string> edges_to_delete_; + std::vector<std::pair<std::string, std::string>> vertices_to_delete_; + rocksdb::Transaction *disk_transaction_; + }; + + std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override { + auto isolation_level = override_isolation_level.value_or(isolation_level_); + if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) { + throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level."); + } + return std::unique_ptr<DiskAccessor>(new DiskAccessor{this, isolation_level, storage_mode_}); + } + + RocksDBStorage *GetRocksDBStorage() const { return kvstore_.get(); } + + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint( + LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override; + + utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint( + LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override; + + Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override; + + private: + void LoadIndexInfoIfExists() const; + + /// TODO (andi): Maybe good to separate these methods and durability kvstore into a separate class + bool PersistLabelIndexCreation(LabelId label) const; + + bool PersistLabelIndexDeletion(LabelId label) const; + + void LoadLabelIndexInfoIfExists() const; + + bool PersistLabelPropertyIndexAndExistenceConstraintCreation(LabelId label, PropertyId property, + const char *key) const; + + bool PersistLabelPropertyIndexAndExistenceConstraintDeletion(LabelId label, PropertyId property, + const char *key) const; + + void LoadLabelPropertyIndexInfoIfExists() const; + + void LoadConstraintsInfoIfExists() const; + + void LoadExistenceConstraintInfoIfExists() const; + + bool PersistUniqueConstraintCreation(LabelId label, const std::set<PropertyId> &properties) const; + + bool PersistUniqueConstraintDeletion(LabelId label, const std::set<PropertyId> &properties) const; + + void LoadUniqueConstraintInfoIfExists() const; + + uint64_t GetDiskSpaceUsage() const; + + void LoadTimestampIfExists(); + + [[nodiscard]] std::optional<ConstraintViolation> CheckExistingVerticesBeforeCreatingExistenceConstraint( + LabelId label, PropertyId property) const; + + [[nodiscard]] utils::BasicResult<ConstraintViolation, std::vector<std::pair<std::string, std::string>>> + CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) const; + + std::vector<std::pair<std::string, std::string>> SerializeVerticesForLabelIndex(LabelId label); + + std::vector<std::pair<std::string, std::string>> SerializeVerticesForLabelPropertyIndex(LabelId label, + PropertyId property); + + StorageInfo GetInfo() const override; + + void FreeMemory(std::unique_lock<utils::RWLock> /*lock*/) override {} + + uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {}); + + std::unique_ptr<RocksDBStorage> kvstore_; + std::unique_ptr<kvstore::KVStore> durability_kvstore_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/unique_constraints.cpp b/src/storage/v2/disk/unique_constraints.cpp new file mode 100644 index 000000000..86e41541f --- /dev/null +++ b/src/storage/v2/disk/unique_constraints.cpp @@ -0,0 +1,349 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/disk/unique_constraints.hpp" +#include <rocksdb/utilities/transaction.h> +#include <limits> +#include <optional> +#include <tuple> +#include "spdlog/spdlog.h" +#include "storage/v2/constraints/unique_constraints.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/algorithm.hpp" +#include "utils/disk_utils.hpp" +#include "utils/file.hpp" +namespace memgraph::storage { + +namespace { + +bool IsVertexUnderConstraint(const Vertex &vertex, const LabelId &constraint_label, + const std::set<PropertyId> &constraint_properties) { + return utils::Contains(vertex.labels, constraint_label) && vertex.properties.HasAllProperties(constraint_properties); +} + +bool IsDifferentVertexWithSameConstraintLabel(const std::string &key, const Gid gid, const LabelId constraint_label) { + const std::vector<std::string> vertex_parts = utils::Split(key, "|"); + if (std::string local_gid = vertex_parts[1]; local_gid == utils::SerializeIdType(gid)) { + return false; + } + return utils::DeserializeConstraintLabelFromUniqueConstraintStorage(key) == constraint_label; +} + +[[nodiscard]] bool ClearTransactionEntriesWithRemovedConstraintLabel( + rocksdb::Transaction &disk_transaction, + const std::map<Gid, std::set<std::pair<LabelId, std::set<PropertyId>>>> &transaction_entries) { + for (const auto &[vertex_gid, constraints] : transaction_entries) { + for (const auto &[constraint_label, constraint_properties] : constraints) { + auto key_to_delete = utils::SerializeVertexAsKeyForUniqueConstraint(constraint_label, constraint_properties, + utils::SerializeIdType(vertex_gid)); + if (auto status = disk_transaction.Delete(key_to_delete); !status.ok()) { + return false; + } + } + } + return true; +} + +} // namespace + +DiskUniqueConstraints::DiskUniqueConstraints(const Config &config) { + kvstore_ = std::make_unique<RocksDBStorage>(); + utils::EnsureDirOrDie(config.disk.unique_constraints_directory); + kvstore_->options_.create_if_missing = true; + kvstore_->options_.comparator = new ComparatorWithU64TsImpl(); + logging::AssertRocksDBStatus(rocksdb::TransactionDB::Open(kvstore_->options_, rocksdb::TransactionDBOptions(), + config.disk.unique_constraints_directory, &kvstore_->db_)); +} + +bool DiskUniqueConstraints::InsertConstraint( + LabelId label, const std::set<PropertyId> &properties, + const std::vector<std::pair<std::string, std::string>> &vertices_under_constraint) { + if (!constraints_.insert(std::make_pair(label, properties)).second) { + return false; + } + + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + for (const auto &[key, value] : vertices_under_constraint) { + disk_transaction->Put(key, value); + } + + /// TODO: figure out a better way to handle this + disk_transaction->SetCommitTimestamp(0); + /// TODO: how about extracting to commit + auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); +} + +std::optional<ConstraintViolation> DiskUniqueConstraints::Validate( + const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage) const { + for (const auto &[constraint_label, constraint_properties] : constraints_) { + if (IsVertexUnderConstraint(vertex, constraint_label, constraint_properties)) { + if (auto vertex_check_result = + TestIfVertexSatisifiesUniqueConstraint(vertex, unique_storage, constraint_label, constraint_properties); + vertex_check_result.has_value()) { + return vertex_check_result.value(); + } + } + } + return std::nullopt; +} + +std::optional<ConstraintViolation> DiskUniqueConstraints::TestIfVertexSatisifiesUniqueConstraint( + const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage, const LabelId &constraint_label, + const std::set<PropertyId> &constraint_properties) const { + auto property_values = vertex.properties.ExtractPropertyValues(constraint_properties); + + /// TODO: better naming. Is vertex unique + if (property_values.has_value() && + VertexIsUnique(property_values.value(), unique_storage, constraint_label, constraint_properties, vertex.gid)) { + unique_storage.emplace_back(std::move(property_values.value())); + return std::nullopt; + } + + return ConstraintViolation{ConstraintViolation::Type::UNIQUE, constraint_label, constraint_properties}; +} + +bool DiskUniqueConstraints::VertexIsUnique(const std::vector<PropertyValue> &property_values, + const std::vector<std::vector<PropertyValue>> &unique_storage, + const LabelId &constraint_label, + const std::set<PropertyId> &constraint_properties, const Gid gid) const { + if (utils::Contains(unique_storage, property_values)) { + return false; + } + + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + disk_transaction->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (IsDifferentVertexWithSameConstraintLabel(it->key().ToString(), gid, constraint_label)) { + if (utils::DeserializePropertiesFromUniqueConstraintStorage(it->value().ToString()) + .ExtractPropertyValues(constraint_properties) == property_values) { + return false; + } + } + } + return true; +} + +bool DiskUniqueConstraints::ClearDeletedVertex(const std::string_view gid, + uint64_t transaction_commit_timestamp) const { + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + disk_transaction->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (std::string key = it->key().ToString(); gid == utils::ExtractGidFromUniqueConstraintStorage(key)) { + if (!disk_transaction->Delete(key).ok()) { + return false; + } + } + } + disk_transaction->SetCommitTimestamp(transaction_commit_timestamp); + auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); +} + +bool DiskUniqueConstraints::DeleteVerticesWithRemovedConstraintLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp) { + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + disk_transaction->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + + bool deletion_success = true; + entries_for_deletion.WithLock([&deletion_success, transaction_start_timestamp, + disk_transaction_ptr = disk_transaction.get()](auto &tx_to_entries_for_deletion) { + if (auto tx_it = tx_to_entries_for_deletion.find(transaction_start_timestamp); + tx_it != tx_to_entries_for_deletion.end()) { + deletion_success = ClearTransactionEntriesWithRemovedConstraintLabel(*disk_transaction_ptr, tx_it->second); + tx_to_entries_for_deletion.erase(tx_it); + } + }); + if (deletion_success) { + /// TODO: Extract to some useful method + disk_transaction->SetCommitTimestamp(transaction_commit_timestamp); + auto status = disk_transaction->Commit(); + if (!status.ok()) { + /// TODO: better naming + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); + } + spdlog::error("Deletetion of vertices with removed constraint label failed."); + return false; +} + +bool DiskUniqueConstraints::SyncVertexToUniqueConstraintsStorage(const Vertex &vertex, + uint64_t commit_timestamp) const { + /// TODO: create method for writing transaction + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + spdlog::debug("Found old disk key {} for vertex {}", maybe_old_disk_key.value(), + utils::SerializeIdType(vertex.gid)); + if (auto status = disk_transaction->Delete(maybe_old_disk_key.value()); !status.ok()) { + return false; + } + } + + for (const auto &[constraint_label, constraint_properties] : constraints_) { + if (IsVertexUnderConstraint(vertex, constraint_label, constraint_properties)) { + auto key = utils::SerializeVertexAsKeyForUniqueConstraint(constraint_label, constraint_properties, + utils::SerializeIdType(vertex.gid)); + auto value = utils::SerializeVertexAsValueForUniqueConstraint(constraint_label, vertex.labels, vertex.properties); + if (!disk_transaction->Put(key, value).ok()) { + return false; + } + } + } + /// TODO: extract and better message + disk_transaction->SetCommitTimestamp(commit_timestamp); + auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } + return status.ok(); +} + +DiskUniqueConstraints::CreationStatus DiskUniqueConstraints::CheckIfConstraintCanBeCreated( + LabelId label, const std::set<PropertyId> &properties) const { + if (properties.empty()) { + return CreationStatus::EMPTY_PROPERTIES; + } + if (properties.size() > kUniqueConstraintsMaxProperties) { + return CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED; + } + if (utils::Contains(constraints_, std::make_pair(label, properties))) { + return CreationStatus::ALREADY_EXISTS; + } + return CreationStatus::SUCCESS; +}; + +DiskUniqueConstraints::DeletionStatus DiskUniqueConstraints::DropConstraint(LabelId label, + const std::set<PropertyId> &properties) { + if (auto drop_properties_check_result = UniqueConstraints::CheckPropertiesBeforeDeletion(properties); + drop_properties_check_result != UniqueConstraints::DeletionStatus::SUCCESS) { + return drop_properties_check_result; + } + if (constraints_.erase({label, properties}) > 0) { + return UniqueConstraints::DeletionStatus::SUCCESS; + } + return UniqueConstraints::DeletionStatus::NOT_FOUND; +} + +bool DiskUniqueConstraints::ConstraintExists(LabelId label, const std::set<PropertyId> &properties) const { + return utils::Contains(constraints_, std::make_pair(label, properties)); +} + +void DiskUniqueConstraints::UpdateOnRemoveLabel(LabelId removed_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) { + for (const auto &constraint : constraints_) { + if (constraint.first == removed_label) { + entries_for_deletion.WithLock( + [&constraint, transaction_start_timestamp, &vertex_before_update](auto &tx_to_entries_for_deletion) { + const auto &[constraint_label, constraint_properties] = constraint; + auto [it, _] = tx_to_entries_for_deletion.emplace( + std::piecewise_construct, std::forward_as_tuple(transaction_start_timestamp), std::forward_as_tuple()); + auto &vertex_map_store = it->second; + auto [it_vertex_map_store, emplaced] = vertex_map_store.emplace( + std::piecewise_construct, std::forward_as_tuple(vertex_before_update.gid), std::forward_as_tuple()); + it_vertex_map_store->second.emplace(constraint_label, constraint_properties); + }); + } + } +} + +void DiskUniqueConstraints::UpdateOnAddLabel(LabelId added_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) { + entries_for_deletion.WithLock( + [transaction_start_timestamp, &vertex_before_update, added_label](auto &tx_to_entries_for_deletion) { + /// TODO: change to only one if condition and maybe optimize erase if + if (auto tx_it = tx_to_entries_for_deletion.find(transaction_start_timestamp); + tx_it != tx_to_entries_for_deletion.end()) { + if (auto vertex_constraints_it = tx_it->second.find(vertex_before_update.gid); + vertex_constraints_it != tx_it->second.end()) { + std::erase_if(vertex_constraints_it->second, + [added_label](const auto &constraint) { return constraint.first == added_label; }); + } + } + }); +} + +std::vector<std::pair<LabelId, std::set<PropertyId>>> DiskUniqueConstraints::ListConstraints() const { + return {constraints_.begin(), constraints_.end()}; +} + +void DiskUniqueConstraints::Clear() { + constraints_.clear(); + + auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( + kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); + disk_transaction->SetReadTimestampForValidation(std::numeric_limits<uint64_t>::max()); + + rocksdb::ReadOptions ro; + std::string strTs = utils::StringTimestamp(std::numeric_limits<uint64_t>::max()); + rocksdb::Slice ts(strTs); + ro.timestamp = &ts; + auto it = std::unique_ptr<rocksdb::Iterator>(disk_transaction->GetIterator(ro)); + + for (it->SeekToFirst(); it->Valid(); it->Next()) { + disk_transaction->Delete(it->key().ToString()); + } + + disk_transaction->SetCommitTimestamp(0); + auto status = disk_transaction->Commit(); + if (!status.ok()) { + spdlog::error("rocksdb: {}", status.getState()); + } +} + +RocksDBStorage *DiskUniqueConstraints::GetRocksDBStorage() const { return kvstore_.get(); } + +void DiskUniqueConstraints::LoadUniqueConstraints(const std::vector<std::string> &keys) { + for (const auto &key : keys) { + std::vector<std::string> key_parts = utils::Split(key, ","); + LabelId label = LabelId::FromUint(std::stoull(key_parts[0])); + std::set<PropertyId> properties; + for (int i = 1; i < key_parts.size(); i++) { + properties.insert(PropertyId::FromUint(std::stoull(key_parts[i]))); + } + constraints_.emplace(std::make_pair(label, properties)); + } +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/unique_constraints.hpp b/src/storage/v2/disk/unique_constraints.hpp new file mode 100644 index 000000000..0cc5a9586 --- /dev/null +++ b/src/storage/v2/disk/unique_constraints.hpp @@ -0,0 +1,78 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/config.hpp" +#include "storage/v2/constraints/unique_constraints.hpp" +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/transaction.hpp" +#include "utils/rocksdb_serialization.hpp" +#include "utils/synchronized.hpp" + +namespace memgraph::storage { + +class DiskUniqueConstraints : public UniqueConstraints { + public: + explicit DiskUniqueConstraints(const Config &config); + + CreationStatus CheckIfConstraintCanBeCreated(LabelId label, const std::set<PropertyId> &properties) const; + + [[nodiscard]] bool InsertConstraint( + LabelId label, const std::set<PropertyId> &properties, + const std::vector<std::pair<std::string, std::string>> &vertices_under_constraint); + + std::optional<ConstraintViolation> Validate(const Vertex &vertex, + std::vector<std::vector<PropertyValue>> &unique_storage) const; + + [[nodiscard]] bool ClearDeletedVertex(std::string_view gid, uint64_t transaction_commit_timestamp) const; + + [[nodiscard]] bool DeleteVerticesWithRemovedConstraintLabel(uint64_t transaction_start_timestamp, + uint64_t transaction_commit_timestamp); + + [[nodiscard]] bool SyncVertexToUniqueConstraintsStorage(const Vertex &vertex, uint64_t commit_timestamp) const; + + DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties) override; + + [[nodiscard]] bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) const override; + + void UpdateOnRemoveLabel(LabelId removed_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) override; + + void UpdateOnAddLabel(LabelId added_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) override; + + std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override; + + void Clear() override; + + RocksDBStorage *GetRocksDBStorage() const; + + void LoadUniqueConstraints(const std::vector<std::string> &keys); + + private: + utils::Synchronized<std::map<uint64_t, std::map<Gid, std::set<std::pair<LabelId, std::set<PropertyId>>>>>> + entries_for_deletion; + std::set<std::pair<LabelId, std::set<PropertyId>>> constraints_; + std::unique_ptr<RocksDBStorage> kvstore_; + + [[nodiscard]] std::optional<ConstraintViolation> TestIfVertexSatisifiesUniqueConstraint( + const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage, const LabelId &constraint_label, + const std::set<PropertyId> &constraint_properties) const; + + bool VertexIsUnique(const std::vector<PropertyValue> &property_values, + const std::vector<std::vector<PropertyValue>> &unique_storage, const LabelId &constraint_label, + const std::set<PropertyId> &constraint_properties, Gid gid) const; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/durability/durability.cpp b/src/storage/v2/durability/durability.cpp index 1f1052528..99b286332 100644 --- a/src/storage/v2/durability/durability.cpp +++ b/src/storage/v2/durability/durability.cpp @@ -27,6 +27,7 @@ #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/snapshot.hpp" #include "storage/v2/durability/wal.hpp" +#include "storage/v2/indices/label_property_index.hpp" #include "utils/event_histogram.hpp" #include "utils/logging.hpp" #include "utils/memory_tracker.hpp" @@ -131,7 +132,8 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ // Recover label indices. spdlog::info("Recreating {} label indices from metadata.", indices_constraints.indices.label.size()); for (const auto &item : indices_constraints.indices.label) { - if (!indices->label_index.CreateIndex(item, vertices->access(), paralell_exec_info)) + auto *mem_label_index = static_cast<InMemoryLabelIndex *>(indices->label_index_.get()); + if (!mem_label_index->CreateIndex(item, vertices->access(), paralell_exec_info)) throw RecoveryFailure("The label index must be created here!"); spdlog::info("A label index is recreated from metadata."); @@ -141,8 +143,9 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ // Recover label+property indices. spdlog::info("Recreating {} label+property indices from metadata.", indices_constraints.indices.label_property.size()); + auto *mem_label_property_index = static_cast<InMemoryLabelPropertyIndex *>(indices->label_property_index_.get()); for (const auto &item : indices_constraints.indices.label_property) { - if (!indices->label_property_index.CreateIndex(item.first, item.second, vertices->access())) + if (!mem_label_property_index->CreateIndex(item.first, item.second, vertices->access(), std::nullopt)) throw RecoveryFailure("The label+property index must be created here!"); spdlog::info("A label+property index is recreated from metadata."); } @@ -152,9 +155,18 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ spdlog::info("Recreating constraints from metadata."); // Recover existence constraints. spdlog::info("Recreating {} existence constraints from metadata.", indices_constraints.constraints.existence.size()); - for (const auto &item : indices_constraints.constraints.existence) { - auto ret = CreateExistenceConstraint(constraints, item.first, item.second, vertices->access()); - if (ret.HasError() || !ret.GetValue()) throw RecoveryFailure("The existence constraint must be created here!"); + for (const auto &[label, property] : indices_constraints.constraints.existence) { + if (constraints->existence_constraints_->ConstraintExists(label, property)) { + throw RecoveryFailure("The existence constraint already exists!"); + } + + if (auto violation = ExistenceConstraints::ValidateVerticesOnConstraint(vertices->access(), label, property); + violation.has_value()) { + throw RecoveryFailure("The existence constraint failed because it couldn't be validated!"); + } + + constraints->existence_constraints_->InsertConstraint(label, property); + spdlog::info("A existence constraint is recreated from metadata."); } spdlog::info("Existence constraints are recreated from metadata."); @@ -162,7 +174,8 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ // Recover unique constraints. spdlog::info("Recreating {} unique constraints from metadata.", indices_constraints.constraints.unique.size()); for (const auto &item : indices_constraints.constraints.unique) { - auto ret = constraints->unique_constraints.CreateConstraint(item.first, item.second, vertices->access()); + auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints->unique_constraints_.get()); + auto ret = mem_unique_constraints->CreateConstraint(item.first, item.second, vertices->access()); if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) throw RecoveryFailure("The unique constraint must be created here!"); spdlog::info("A unique constraint is recreated from metadata."); diff --git a/src/storage/v2/durability/durability.hpp b/src/storage/v2/durability/durability.hpp index 1af07136f..2e13a776a 100644 --- a/src/storage/v2/durability/durability.hpp +++ b/src/storage/v2/durability/durability.hpp @@ -19,11 +19,11 @@ #include <variant> #include "storage/v2/config.hpp" -#include "storage/v2/constraints.hpp" +#include "storage/v2/constraints/constraints.hpp" #include "storage/v2/durability/metadata.hpp" #include "storage/v2/durability/wal.hpp" #include "storage/v2/edge.hpp" -#include "storage/v2/indices.hpp" +#include "storage/v2/indices/indices.hpp" #include "storage/v2/name_id_mapper.hpp" #include "storage/v2/vertex.hpp" #include "utils/skip_list.hpp" diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp index 61c159c99..7e9ff0289 100644 --- a/src/storage/v2/durability/snapshot.cpp +++ b/src/storage/v2/durability/snapshot.cpp @@ -1395,6 +1395,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps is_visible = true; break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { is_visible = false; break; @@ -1517,7 +1518,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Write label indices. { - auto label = indices->label_index.ListIndices(); + auto label = indices->label_index_->ListIndices(); snapshot.WriteUint(label.size()); for (const auto &item : label) { write_mapping(item); @@ -1526,7 +1527,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Write label+property indices. { - auto label_property = indices->label_property_index.ListIndices(); + auto label_property = indices->label_property_index_->ListIndices(); snapshot.WriteUint(label_property.size()); for (const auto &item : label_property) { write_mapping(item.first); @@ -1542,7 +1543,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Write existence constraints. { - auto existence = ListExistenceConstraints(*constraints); + auto existence = constraints->existence_constraints_->ListConstraints(); snapshot.WriteUint(existence.size()); for (const auto &item : existence) { write_mapping(item.first); @@ -1552,7 +1553,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Write unique constraints. { - auto unique = constraints->unique_constraints.ListConstraints(); + auto unique = constraints->unique_constraints_->ListConstraints(); snapshot.WriteUint(unique.size()); for (const auto &item : unique) { write_mapping(item.first); diff --git a/src/storage/v2/durability/snapshot.hpp b/src/storage/v2/durability/snapshot.hpp index 41b91751b..2f16088a0 100644 --- a/src/storage/v2/durability/snapshot.hpp +++ b/src/storage/v2/durability/snapshot.hpp @@ -16,10 +16,10 @@ #include <string> #include "storage/v2/config.hpp" -#include "storage/v2/constraints.hpp" +#include "storage/v2/constraints/constraints.hpp" #include "storage/v2/durability/metadata.hpp" #include "storage/v2/edge.hpp" -#include "storage/v2/indices.hpp" +#include "storage/v2/indices/indices.hpp" #include "storage/v2/name_id_mapper.hpp" #include "storage/v2/transaction.hpp" #include "storage/v2/vertex.hpp" diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp index a8faf064a..570520bbd 100644 --- a/src/storage/v2/durability/wal.cpp +++ b/src/storage/v2/durability/wal.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -101,6 +101,7 @@ Marker VertexActionToMarker(Delta::Action action) { // because the Delta's represent undo actions and we want to store redo // actions. switch (action) { + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: return Marker::DELTA_VERTEX_CREATE; case Delta::Action::RECREATE_OBJECT: @@ -491,6 +492,7 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite encoder->WriteUint(timestamp); std::lock_guard<utils::SpinLock> guard(vertex.lock); switch (delta.action) { + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: case Delta::Action::RECREATE_OBJECT: { encoder->WriteMarker(VertexActionToMarker(delta.action)); @@ -558,6 +560,7 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta encoder->WritePropertyValue(edge.properties.GetProperty(delta.property.key)); break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: case Delta::Action::RECREATE_OBJECT: // These actions are already encoded in vertex *_OUT_EDGE actions. Also, diff --git a/src/storage/v2/edge.hpp b/src/storage/v2/edge.hpp index fcf8ff8e7..89bbc83e5 100644 --- a/src/storage/v2/edge.hpp +++ b/src/storage/v2/edge.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,7 +25,8 @@ struct Vertex; struct Edge { Edge(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, + MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT || + delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT, "Edge must be created with an initial DELETE_OBJECT delta!"); } diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp index d14c8d56d..1c47e7102 100644 --- a/src/storage/v2/edge_accessor.cpp +++ b/src/storage/v2/edge_accessor.cpp @@ -14,6 +14,7 @@ #include <memory> #include <tuple> +#include "storage/v2/delta.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/vertex_accessor.hpp" @@ -44,6 +45,7 @@ bool EdgeAccessor::IsVisible(const View view) const { case Delta::Action::REMOVE_IN_EDGE: case Delta::Action::ADD_IN_EDGE: case Delta::Action::RECREATE_OBJECT: + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: break; case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge @@ -83,6 +85,7 @@ bool EdgeAccessor::IsVisible(const View view) const { deleted = false; break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -181,6 +184,7 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -232,6 +236,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index ee8176365..93e995a05 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -88,7 +88,6 @@ class EdgeAccessor final { } bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); } - private: EdgeRef edge_; EdgeTypeId edge_type_; Vertex *from_vertex_; diff --git a/src/storage/v2/indices.cpp b/src/storage/v2/indices.cpp deleted file mode 100644 index 596631899..000000000 --- a/src/storage/v2/indices.cpp +++ /dev/null @@ -1,907 +0,0 @@ -// Copyright 2023 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#include "indices.hpp" -#include <algorithm> -#include <iterator> -#include <limits> -#include <thread> - -#include "storage/v2/mvcc.hpp" -#include "storage/v2/property_value.hpp" -#include "utils/bound.hpp" -#include "utils/logging.hpp" -#include "utils/memory_tracker.hpp" -#include "utils/synchronized.hpp" - -namespace memgraph::storage { - -namespace { - -/// Traverses deltas visible from transaction with start timestamp greater than -/// the provided timestamp, and calls the provided callback function for each -/// delta. If the callback ever returns true, traversal is stopped and the -/// function returns true. Otherwise, the function returns false. -template <typename TCallback> -bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) { - while (delta != nullptr) { - auto ts = delta->timestamp->load(std::memory_order_acquire); - // This is a committed change that we see so we shouldn't undo it. - if (ts < timestamp) { - break; - } - if (predicate(*delta)) { - return true; - } - // Move to the next delta. - delta = delta->next.load(std::memory_order_acquire); - } - return false; -} - -/// Helper function for label index garbage collection. Returns true if there's -/// a reachable version of the vertex that has the given label. -bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp) { - bool has_label; - bool deleted; - const Delta *delta; - { - std::lock_guard<utils::SpinLock> guard(vertex.lock); - has_label = utils::Contains(vertex.labels, label); - deleted = vertex.deleted; - delta = vertex.delta; - } - if (!deleted && has_label) { - return true; - } - return AnyVersionSatisfiesPredicate(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) { - switch (delta.action) { - case Delta::Action::ADD_LABEL: - if (delta.label == label) { - MG_ASSERT(!has_label, "Invalid database state!"); - has_label = true; - } - break; - case Delta::Action::REMOVE_LABEL: - if (delta.label == label) { - MG_ASSERT(has_label, "Invalid database state!"); - has_label = false; - } - break; - case Delta::Action::RECREATE_OBJECT: { - MG_ASSERT(deleted, "Invalid database state!"); - deleted = false; - break; - } - case Delta::Action::DELETE_OBJECT: { - MG_ASSERT(!deleted, "Invalid database state!"); - deleted = true; - break; - } - 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; - } - return !deleted && has_label; - }); -} - -/// Helper function for label-property index garbage collection. Returns true if -/// there's a reachable version of the vertex that has the given label and -/// property value. -bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, const PropertyValue &value, - uint64_t timestamp) { - bool has_label; - bool current_value_equal_to_value = value.IsNull(); - bool deleted; - const Delta *delta; - { - std::lock_guard<utils::SpinLock> guard(vertex.lock); - has_label = utils::Contains(vertex.labels, label); - current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); - deleted = vertex.deleted; - delta = vertex.delta; - } - - if (!deleted && has_label && current_value_equal_to_value) { - return true; - } - - return AnyVersionSatisfiesPredicate( - timestamp, delta, [&has_label, ¤t_value_equal_to_value, &deleted, label, key, &value](const Delta &delta) { - switch (delta.action) { - case Delta::Action::ADD_LABEL: - if (delta.label == label) { - MG_ASSERT(!has_label, "Invalid database state!"); - has_label = true; - } - break; - case Delta::Action::REMOVE_LABEL: - if (delta.label == label) { - MG_ASSERT(has_label, "Invalid database state!"); - has_label = false; - } - break; - case Delta::Action::SET_PROPERTY: - if (delta.property.key == key) { - current_value_equal_to_value = delta.property.value == value; - } - break; - case Delta::Action::RECREATE_OBJECT: { - MG_ASSERT(deleted, "Invalid database state!"); - deleted = false; - break; - } - case Delta::Action::DELETE_OBJECT: { - MG_ASSERT(!deleted, "Invalid database state!"); - deleted = true; - break; - } - 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; - } - return !deleted && has_label && current_value_equal_to_value; - }); -} - -// Helper function for iterating through label index. Returns true if this -// transaction can see the given vertex, and the visible version has the given -// label. -bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *transaction, View view) { - bool deleted; - bool has_label; - const Delta *delta; - { - std::lock_guard<utils::SpinLock> guard(vertex.lock); - deleted = vertex.deleted; - has_label = utils::Contains(vertex.labels, label); - delta = vertex.delta; - } - ApplyDeltasForRead(transaction, delta, view, [&deleted, &has_label, label](const Delta &delta) { - switch (delta.action) { - case Delta::Action::REMOVE_LABEL: { - if (delta.label == label) { - MG_ASSERT(has_label, "Invalid database state!"); - has_label = false; - } - break; - } - case Delta::Action::ADD_LABEL: { - if (delta.label == label) { - MG_ASSERT(!has_label, "Invalid database state!"); - has_label = true; - } - break; - } - case Delta::Action::DELETE_OBJECT: { - MG_ASSERT(!deleted, "Invalid database state!"); - deleted = true; - break; - } - case Delta::Action::RECREATE_OBJECT: { - MG_ASSERT(deleted, "Invalid database state!"); - deleted = false; - break; - } - 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; - } - }); - return !deleted && has_label; -} - -// Helper function for iterating through label-property index. Returns true if -// this transaction can see the given vertex, and the visible version has the -// given label and property. -bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, const PropertyValue &value, - Transaction *transaction, View view) { - bool deleted; - bool has_label; - bool current_value_equal_to_value = value.IsNull(); - const Delta *delta; - { - std::lock_guard<utils::SpinLock> guard(vertex.lock); - deleted = vertex.deleted; - has_label = utils::Contains(vertex.labels, label); - current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); - delta = vertex.delta; - } - ApplyDeltasForRead(transaction, delta, view, - [&deleted, &has_label, ¤t_value_equal_to_value, key, label, &value](const Delta &delta) { - switch (delta.action) { - case Delta::Action::SET_PROPERTY: { - if (delta.property.key == key) { - current_value_equal_to_value = delta.property.value == value; - } - break; - } - case Delta::Action::DELETE_OBJECT: { - MG_ASSERT(!deleted, "Invalid database state!"); - deleted = true; - break; - } - case Delta::Action::RECREATE_OBJECT: { - MG_ASSERT(deleted, "Invalid database state!"); - deleted = false; - break; - } - case Delta::Action::ADD_LABEL: - if (delta.label == label) { - MG_ASSERT(!has_label, "Invalid database state!"); - has_label = true; - } - break; - case Delta::Action::REMOVE_LABEL: - if (delta.label == label) { - MG_ASSERT(has_label, "Invalid database state!"); - has_label = false; - } - break; - 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; - } - }); - return !deleted && has_label && current_value_equal_to_value; -} - -template <typename TIndexAccessor> -void TryInsertLabelIndex(Vertex &vertex, LabelId label, TIndexAccessor &index_accessor) { - if (vertex.deleted || !utils::Contains(vertex.labels, label)) { - return; - } - - index_accessor.insert({&vertex, 0}); -} - -template <typename TIndexAccessor> -void TryInsertLabelPropertyIndex(Vertex &vertex, std::pair<LabelId, PropertyId> label_property_pair, - TIndexAccessor &index_accessor) { - if (vertex.deleted || !utils::Contains(vertex.labels, label_property_pair.first)) { - return; - } - auto value = vertex.properties.GetProperty(label_property_pair.second); - if (value.IsNull()) { - return; - } - index_accessor.insert({std::move(value), &vertex, 0}); -} - -template <typename TSkiplistIter, typename TIndex, typename TIndexKey, typename TFunc> -void CreateIndexOnSingleThread(utils::SkipList<Vertex>::Accessor &vertices, TSkiplistIter it, TIndex &index, - TIndexKey key, const TFunc &func) { - utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - try { - auto acc = it->second.access(); - for (Vertex &vertex : vertices) { - func(vertex, key, acc); - } - } catch (const utils::OutOfMemoryException &) { - utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_exception_blocker; - index.erase(it); - throw; - } -} - -template <typename TIndex, typename TIndexKey, typename TSKiplistIter, typename TFunc> -void CreateIndexOnMultipleThreads(utils::SkipList<Vertex>::Accessor &vertices, TSKiplistIter skiplist_iter, - TIndex &index, TIndexKey key, const ParalellizedIndexCreationInfo ¶lell_exec_info, - const TFunc &func) { - utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - - const auto &vertex_batches = paralell_exec_info.first; - const auto thread_count = std::min(paralell_exec_info.second, vertex_batches.size()); - - MG_ASSERT(!vertex_batches.empty(), - "The size of batches should always be greater than zero if you want to use the parallel version of index " - "creation!"); - - std::atomic<uint64_t> batch_counter = 0; - - utils::Synchronized<std::optional<utils::OutOfMemoryException>, utils::SpinLock> maybe_error{}; - { - std::vector<std::jthread> threads; - threads.reserve(thread_count); - - for (auto i{0U}; i < thread_count; ++i) { - threads.emplace_back( - [&skiplist_iter, &func, &index, &vertex_batches, &maybe_error, &batch_counter, &key, &vertices]() { - while (!maybe_error.Lock()->has_value()) { - const auto batch_index = batch_counter++; - if (batch_index >= vertex_batches.size()) { - return; - } - const auto &batch = vertex_batches[batch_index]; - auto index_accessor = index.at(key).access(); - auto it = vertices.find(batch.first); - - try { - for (auto i{0U}; i < batch.second; ++i, ++it) { - func(*it, key, index_accessor); - } - - } catch (utils::OutOfMemoryException &failure) { - utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_exception_blocker; - index.erase(skiplist_iter); - *maybe_error.Lock() = std::move(failure); - } - } - }); - } - } - if (maybe_error.Lock()->has_value()) { - throw utils::OutOfMemoryException((*maybe_error.Lock())->what()); - } -} - -} // namespace - -void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) { - auto it = index_.find(label); - if (it == index_.end()) return; - auto acc = it->second.access(); - acc.insert(Entry{vertex, tx.start_timestamp}); -} - -bool LabelIndex::CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices, - const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info) { - auto create_index_seq = [this](LabelId label, utils::SkipList<Vertex>::Accessor &vertices, - std::map<LabelId, utils::SkipList<Entry>>::iterator it) { - using IndexAccessor = decltype(it->second.access()); - - CreateIndexOnSingleThread(vertices, it, index_, label, - [](Vertex &vertex, LabelId label, IndexAccessor &index_accessor) { - TryInsertLabelIndex(vertex, label, index_accessor); - }); - - return true; - }; - - auto create_index_par = [this](LabelId label, utils::SkipList<Vertex>::Accessor &vertices, - std::map<LabelId, utils::SkipList<Entry>>::iterator label_it, - const ParalellizedIndexCreationInfo ¶lell_exec_info) { - using IndexAccessor = decltype(label_it->second.access()); - - CreateIndexOnMultipleThreads(vertices, label_it, index_, label, paralell_exec_info, - [](Vertex &vertex, LabelId label, IndexAccessor &index_accessor) { - TryInsertLabelIndex(vertex, label, index_accessor); - }); - - return true; - }; - - auto [it, emplaced] = index_.emplace(std::piecewise_construct, std::forward_as_tuple(label), std::forward_as_tuple()); - if (!emplaced) { - // Index already exists. - return false; - } - - if (paralell_exec_info) { - return create_index_par(label, vertices, it, *paralell_exec_info); - } - return create_index_seq(label, vertices, it); -} - -std::vector<LabelId> LabelIndex::ListIndices() const { - std::vector<LabelId> ret; - ret.reserve(index_.size()); - for (const auto &item : index_) { - ret.push_back(item.first); - } - return ret; -} - -void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { - for (auto &label_storage : index_) { - auto vertices_acc = label_storage.second.access(); - for (auto it = vertices_acc.begin(); it != vertices_acc.end();) { - auto next_it = it; - ++next_it; - - if (it->timestamp >= oldest_active_start_timestamp) { - it = next_it; - continue; - } - - if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) || - !AnyVersionHasLabel(*it->vertex, label_storage.first, oldest_active_start_timestamp)) { - vertices_acc.remove(*it); - } - - it = next_it; - } - } -} - -LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator) - : self_(self), - index_iterator_(index_iterator), - current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_), - current_vertex_(nullptr) { - AdvanceUntilValid(); -} - -LabelIndex::Iterable::Iterator &LabelIndex::Iterable::Iterator::operator++() { - ++index_iterator_; - AdvanceUntilValid(); - return *this; -} - -void LabelIndex::Iterable::Iterator::AdvanceUntilValid() { - for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) { - if (index_iterator_->vertex == current_vertex_) { - continue; - } - if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) { - current_vertex_ = index_iterator_->vertex; - current_vertex_accessor_ = - VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_}; - break; - } - } -} - -LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, - Transaction *transaction, Indices *indices, Constraints *constraints, - Config::Items config) - : index_accessor_(std::move(index_accessor)), - label_(label), - view_(view), - transaction_(transaction), - indices_(indices), - constraints_(constraints), - config_(config) {} - -void LabelIndex::RunGC() { - for (auto &index_entry : index_) { - index_entry.second.run_gc(); - } -} - -void LabelIndex::SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats) { - stats_[label] = stats; -} - -std::optional<LabelIndexStats> LabelIndex::GetIndexStats(const storage::LabelId &label) const { - if (auto it = stats_.find(label); it != stats_.end()) { - return it->second; - } - return {}; -} - -std::vector<LabelId> LabelIndex::ClearIndexStats() { - std::vector<LabelId> deleted_indexes; - deleted_indexes.reserve(stats_.size()); - std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes), - [](const auto &elem) { return elem.first; }); - stats_.clear(); - return deleted_indexes; -} - -std::vector<LabelId> LabelIndex::DeleteIndexStats(const storage::LabelId &label) { - std::vector<LabelId> deleted_indexes; - for (auto it = stats_.cbegin(); it != stats_.cend();) { - if (it->first == label) { - deleted_indexes.push_back(it->first); - it = stats_.erase(it); - } else { - ++it; - } - } - - return deleted_indexes; -} - -bool LabelPropertyIndex::Entry::operator<(const Entry &rhs) { - if (value < rhs.value) { - return true; - } - if (rhs.value < value) { - return false; - } - return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp); -} - -bool LabelPropertyIndex::Entry::operator==(const Entry &rhs) { - return value == rhs.value && vertex == rhs.vertex && timestamp == rhs.timestamp; -} - -bool LabelPropertyIndex::Entry::operator<(const PropertyValue &rhs) { return value < rhs; } - -bool LabelPropertyIndex::Entry::operator==(const PropertyValue &rhs) { return value == rhs; } - -void LabelPropertyIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) { - for (auto &[label_prop, storage] : index_) { - if (label_prop.first != label) { - continue; - } - auto prop_value = vertex->properties.GetProperty(label_prop.second); - if (!prop_value.IsNull()) { - auto acc = storage.access(); - acc.insert(Entry{std::move(prop_value), vertex, tx.start_timestamp}); - } - } -} - -void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, - const Transaction &tx) { - if (value.IsNull()) { - return; - } - for (auto &[label_prop, storage] : index_) { - if (label_prop.second != property) { - continue; - } - if (utils::Contains(vertex->labels, label_prop.first)) { - auto acc = storage.access(); - acc.insert(Entry{value, vertex, tx.start_timestamp}); - } - } -} - -bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices, - const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info) { - auto create_index_seq = [this](LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor &vertices, - std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>>::iterator it) { - using IndexAccessor = decltype(it->second.access()); - - CreateIndexOnSingleThread(vertices, it, index_, std::make_pair(label, property), - [](Vertex &vertex, std::pair<LabelId, PropertyId> key, IndexAccessor &index_accessor) { - TryInsertLabelPropertyIndex(vertex, key, index_accessor); - }); - - return true; - }; - - auto create_index_par = - [this](LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor &vertices, - std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>>::iterator label_property_it, - const ParalellizedIndexCreationInfo ¶lell_exec_info) { - using IndexAccessor = decltype(label_property_it->second.access()); - - CreateIndexOnMultipleThreads( - vertices, label_property_it, index_, std::make_pair(label, property), paralell_exec_info, - [](Vertex &vertex, std::pair<LabelId, PropertyId> key, IndexAccessor &index_accessor) { - TryInsertLabelPropertyIndex(vertex, key, index_accessor); - }); - - return true; - }; - - auto [it, emplaced] = - index_.emplace(std::piecewise_construct, std::forward_as_tuple(label, property), std::forward_as_tuple()); - if (!emplaced) { - // Index already exists. - return false; - } - - if (paralell_exec_info) { - return create_index_par(label, property, vertices, it, *paralell_exec_info); - } - return create_index_seq(label, property, vertices, it); -} - -std::vector<std::pair<LabelId, PropertyId>> LabelPropertyIndex::ListIndices() const { - std::vector<std::pair<LabelId, PropertyId>> ret; - ret.reserve(index_.size()); - for (const auto &item : index_) { - ret.push_back(item.first); - } - return ret; -} - -void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { - for (auto &[label_property, index] : index_) { - auto index_acc = index.access(); - for (auto it = index_acc.begin(); it != index_acc.end();) { - auto next_it = it; - ++next_it; - - if (it->timestamp >= oldest_active_start_timestamp) { - it = next_it; - continue; - } - - if ((next_it != index_acc.end() && it->vertex == next_it->vertex && it->value == next_it->value) || - !AnyVersionHasLabelProperty(*it->vertex, label_property.first, label_property.second, it->value, - oldest_active_start_timestamp)) { - index_acc.remove(*it); - } - it = next_it; - } - } -} - -LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator) - : self_(self), - index_iterator_(index_iterator), - current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_), - current_vertex_(nullptr) { - AdvanceUntilValid(); -} - -LabelPropertyIndex::Iterable::Iterator &LabelPropertyIndex::Iterable::Iterator::operator++() { - ++index_iterator_; - AdvanceUntilValid(); - return *this; -} - -void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() { - for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) { - if (index_iterator_->vertex == current_vertex_) { - continue; - } - - if (self_->lower_bound_) { - if (index_iterator_->value < self_->lower_bound_->value()) { - continue; - } - if (!self_->lower_bound_->IsInclusive() && index_iterator_->value == self_->lower_bound_->value()) { - continue; - } - } - if (self_->upper_bound_) { - if (self_->upper_bound_->value() < index_iterator_->value) { - index_iterator_ = self_->index_accessor_.end(); - break; - } - if (!self_->upper_bound_->IsInclusive() && index_iterator_->value == self_->upper_bound_->value()) { - index_iterator_ = self_->index_accessor_.end(); - break; - } - } - - if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_, - index_iterator_->value, self_->transaction_, self_->view_)) { - current_vertex_ = index_iterator_->vertex; - current_vertex_accessor_ = - VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_); - break; - } - } -} - -// These constants represent the smallest possible value of each type that is -// contained in a `PropertyValue`. Note that numbers (integers and doubles) are -// treated as the same "type" in `PropertyValue`. -const PropertyValue kSmallestBool = PropertyValue(false); -static_assert(-std::numeric_limits<double>::infinity() < std::numeric_limits<int64_t>::min()); -const PropertyValue kSmallestNumber = PropertyValue(-std::numeric_limits<double>::infinity()); -const PropertyValue kSmallestString = PropertyValue(""); -const PropertyValue kSmallestList = PropertyValue(std::vector<PropertyValue>()); -const PropertyValue kSmallestMap = PropertyValue(std::map<std::string, PropertyValue>()); -const PropertyValue kSmallestTemporalData = - PropertyValue(TemporalData{static_cast<TemporalType>(0), std::numeric_limits<int64_t>::min()}); - -LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, - PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower_bound, - const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, - Transaction *transaction, Indices *indices, Constraints *constraints, - Config::Items config) - : index_accessor_(std::move(index_accessor)), - label_(label), - property_(property), - lower_bound_(lower_bound), - upper_bound_(upper_bound), - view_(view), - transaction_(transaction), - indices_(indices), - constraints_(constraints), - config_(config) { - // We have to fix the bounds that the user provided to us. If the user - // provided only one bound we should make sure that only values of that type - // are returned by the iterator. We ensure this by supplying either an - // inclusive lower bound of the same type, or an exclusive upper bound of the - // following type. If neither bound is set we yield all items in the index. - - // First we statically verify that our assumptions about the `PropertyValue` - // type ordering holds. - static_assert(PropertyValue::Type::Bool < PropertyValue::Type::Int); - static_assert(PropertyValue::Type::Int < PropertyValue::Type::Double); - static_assert(PropertyValue::Type::Double < PropertyValue::Type::String); - static_assert(PropertyValue::Type::String < PropertyValue::Type::List); - static_assert(PropertyValue::Type::List < PropertyValue::Type::Map); - - // Remove any bounds that are set to `Null` because that isn't a valid value. - if (lower_bound_ && lower_bound_->value().IsNull()) { - lower_bound_ = std::nullopt; - } - if (upper_bound_ && upper_bound_->value().IsNull()) { - upper_bound_ = std::nullopt; - } - - // Check whether the bounds are of comparable types if both are supplied. - if (lower_bound_ && upper_bound_ && - !PropertyValue::AreComparableTypes(lower_bound_->value().type(), upper_bound_->value().type())) { - bounds_valid_ = false; - return; - } - - // Set missing bounds. - if (lower_bound_ && !upper_bound_) { - // Here we need to supply an upper bound. The upper bound is set to an - // exclusive lower bound of the following type. - switch (lower_bound_->value().type()) { - case PropertyValue::Type::Null: - // This shouldn't happen because of the nullopt-ing above. - LOG_FATAL("Invalid database state!"); - break; - case PropertyValue::Type::Bool: - upper_bound_ = utils::MakeBoundExclusive(kSmallestNumber); - break; - case PropertyValue::Type::Int: - case PropertyValue::Type::Double: - // Both integers and doubles are treated as the same type in - // `PropertyValue` and they are interleaved when sorted. - upper_bound_ = utils::MakeBoundExclusive(kSmallestString); - break; - case PropertyValue::Type::String: - upper_bound_ = utils::MakeBoundExclusive(kSmallestList); - break; - case PropertyValue::Type::List: - upper_bound_ = utils::MakeBoundExclusive(kSmallestMap); - break; - case PropertyValue::Type::Map: - upper_bound_ = utils::MakeBoundExclusive(kSmallestTemporalData); - break; - case PropertyValue::Type::TemporalData: - // This is the last type in the order so we leave the upper bound empty. - break; - } - } - if (upper_bound_ && !lower_bound_) { - // Here we need to supply a lower bound. The lower bound is set to an - // inclusive lower bound of the current type. - switch (upper_bound_->value().type()) { - case PropertyValue::Type::Null: - // This shouldn't happen because of the nullopt-ing above. - LOG_FATAL("Invalid database state!"); - break; - case PropertyValue::Type::Bool: - lower_bound_ = utils::MakeBoundInclusive(kSmallestBool); - break; - case PropertyValue::Type::Int: - case PropertyValue::Type::Double: - // Both integers and doubles are treated as the same type in - // `PropertyValue` and they are interleaved when sorted. - lower_bound_ = utils::MakeBoundInclusive(kSmallestNumber); - break; - case PropertyValue::Type::String: - lower_bound_ = utils::MakeBoundInclusive(kSmallestString); - break; - case PropertyValue::Type::List: - lower_bound_ = utils::MakeBoundInclusive(kSmallestList); - break; - case PropertyValue::Type::Map: - lower_bound_ = utils::MakeBoundInclusive(kSmallestMap); - break; - case PropertyValue::Type::TemporalData: - lower_bound_ = utils::MakeBoundInclusive(kSmallestTemporalData); - break; - } - } -} - -LabelPropertyIndex::Iterable::Iterator LabelPropertyIndex::Iterable::begin() { - // If the bounds are set and don't have comparable types we don't yield any - // items from the index. - if (!bounds_valid_) return Iterator(this, index_accessor_.end()); - auto index_iterator = index_accessor_.begin(); - if (lower_bound_) { - index_iterator = index_accessor_.find_equal_or_greater(lower_bound_->value()); - } - return Iterator(this, index_iterator); -} - -LabelPropertyIndex::Iterable::Iterator LabelPropertyIndex::Iterable::end() { - return Iterator(this, index_accessor_.end()); -} - -int64_t LabelPropertyIndex::ApproximateVertexCount(LabelId label, PropertyId property, - const PropertyValue &value) const { - auto it = index_.find({label, property}); - MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); - auto acc = it->second.access(); - if (!value.IsNull()) { - return acc.estimate_count(value, utils::SkipListLayerForCountEstimation(acc.size())); - } else { - // The value `Null` won't ever appear in the index because it indicates that - // the property shouldn't exist. Instead, this value is used as an indicator - // to estimate the average number of equal elements in the list (for any - // given value). - return acc.estimate_average_number_of_equals( - [](const auto &first, const auto &second) { return first.value == second.value; }, - utils::SkipListLayerForAverageEqualsEstimation(acc.size())); - } -} - -int64_t LabelPropertyIndex::ApproximateVertexCount(LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower, - const std::optional<utils::Bound<PropertyValue>> &upper) const { - auto it = index_.find({label, property}); - MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); - auto acc = it->second.access(); - return acc.estimate_range_count(lower, upper, utils::SkipListLayerForCountEstimation(acc.size())); -} - -/* -Iterate over all property-label pairs and deletes if label from the index is equal to label parameter. -*/ -std::vector<std::pair<LabelId, PropertyId>> LabelPropertyIndex::DeleteIndexStats(const storage::LabelId &label) { - std::vector<std::pair<LabelId, PropertyId>> deleted_indexes; - for (auto it = stats_.cbegin(); it != stats_.cend();) { - if (it->first.first == label) { - deleted_indexes.push_back(it->first); - it = stats_.erase(it); - } else { - ++it; - } - } - return deleted_indexes; -} - -std::vector<std::pair<LabelId, PropertyId>> LabelPropertyIndex::ClearIndexStats() { - std::vector<std::pair<LabelId, PropertyId>> deleted_indexes; - deleted_indexes.reserve(stats_.size()); - std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes), - [](const auto &elem) { return elem.first; }); - stats_.clear(); - return deleted_indexes; -} - -void LabelPropertyIndex::SetIndexStats(const std::pair<storage::LabelId, storage::PropertyId> &key, - const storage::LabelPropertyIndexStats &stats) { - stats_[key] = stats; -} - -std::optional<storage::LabelPropertyIndexStats> LabelPropertyIndex::GetIndexStats( - const std::pair<storage::LabelId, storage::PropertyId> &key) const { - if (auto it = stats_.find(key); it != stats_.end()) { - return it->second; - } - return {}; -} - -void LabelPropertyIndex::RunGC() { - for (auto &index_entry : index_) { - index_entry.second.run_gc(); - } -} - -void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp) { - indices->label_index.RemoveObsoleteEntries(oldest_active_start_timestamp); - indices->label_property_index.RemoveObsoleteEntries(oldest_active_start_timestamp); -} - -void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex, const Transaction &tx) { - indices->label_index.UpdateOnAddLabel(label, vertex, tx); - indices->label_property_index.UpdateOnAddLabel(label, vertex, tx); -} - -void UpdateOnSetProperty(Indices *indices, PropertyId property, const PropertyValue &value, Vertex *vertex, - const Transaction &tx) { - indices->label_property_index.UpdateOnSetProperty(property, value, vertex, tx); -} - -} // namespace memgraph::storage diff --git a/src/storage/v2/indices.hpp b/src/storage/v2/indices.hpp deleted file mode 100644 index b5dc28114..000000000 --- a/src/storage/v2/indices.hpp +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2023 Memgraph Ltd. -// -// Use of this software is governed by the Business Source License -// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source -// License, and you may not use this file except in compliance with the Business Source License. -// -// As of the Change Date specified in that file, in accordance with -// the Business Source License, use of this software will be governed -// by the Apache License, Version 2.0, included in the file -// licenses/APL.txt. - -#pragma once - -#include <optional> -#include <tuple> -#include <utility> - -#include "storage/v2/config.hpp" -#include "storage/v2/property_value.hpp" -#include "storage/v2/transaction.hpp" -#include "storage/v2/vertex_accessor.hpp" -#include "utils/bound.hpp" -#include "utils/logging.hpp" -#include "utils/skip_list.hpp" - -namespace memgraph::storage { - -struct Indices; -struct Constraints; - -using ParalellizedIndexCreationInfo = - std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>; - -struct LabelIndexStats { - uint64_t count; - double avg_degree; -}; - -class LabelIndex { - private: - struct Entry { - Vertex *vertex; - uint64_t timestamp; - - bool operator<(const Entry &rhs) { - return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp); - } - bool operator==(const Entry &rhs) { return vertex == rhs.vertex && timestamp == rhs.timestamp; } - }; - - struct LabelStorage { - LabelId label; - utils::SkipList<Entry> vertices; - - bool operator<(const LabelStorage &rhs) { return label < rhs.label; } - bool operator<(LabelId rhs) { return label < rhs; } - bool operator==(const LabelStorage &rhs) { return label == rhs.label; } - bool operator==(LabelId rhs) { return label == rhs; } - }; - - public: - LabelIndex(Indices *indices, Constraints *constraints, Config::Items config) - : indices_(indices), constraints_(constraints), config_(config) {} - - /// @throw std::bad_alloc - void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx); - - /// @throw std::bad_alloc - bool CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices, - const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info = std::nullopt); - - /// Returns false if there was no index to drop - bool DropIndex(LabelId label) { return index_.erase(label) > 0; } - - bool IndexExists(LabelId label) const { return index_.find(label) != index_.end(); } - - std::vector<LabelId> ListIndices() const; - - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); - - class Iterable { - public: - Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction, - Indices *indices, Constraints *constraints, Config::Items config); - - class Iterator { - public: - Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator); - - VertexAccessor operator*() const { return current_vertex_accessor_; } - - bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; } - bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; } - - Iterator &operator++(); - - private: - void AdvanceUntilValid(); - - Iterable *self_; - utils::SkipList<Entry>::Iterator index_iterator_; - VertexAccessor current_vertex_accessor_; - Vertex *current_vertex_; - }; - - Iterator begin() { return Iterator(this, index_accessor_.begin()); } - Iterator end() { return Iterator(this, index_accessor_.end()); } - - private: - utils::SkipList<Entry>::Accessor index_accessor_; - LabelId label_; - View view_; - Transaction *transaction_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; - }; - - /// Returns an self with vertices visible from the given transaction. - Iterable Vertices(LabelId label, View view, Transaction *transaction) { - auto it = index_.find(label); - MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint()); - return Iterable(it->second.access(), label, view, transaction, indices_, constraints_, config_); - } - - int64_t ApproximateVertexCount(LabelId label) { - auto it = index_.find(label); - MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint()); - return it->second.size(); - } - - void SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats); - - std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const; - - std::vector<LabelId> ClearIndexStats(); - - std::vector<LabelId> DeleteIndexStats(const storage::LabelId &label); - - void Clear() { index_.clear(); } - - void RunGC(); - - private: - std::map<LabelId, utils::SkipList<Entry>> index_; - std::map<LabelId, storage::LabelIndexStats> stats_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; -}; - -struct LabelPropertyIndexStats { - uint64_t count, distinct_values_count; - double statistic, avg_group_size, avg_degree; -}; - -class LabelPropertyIndex { - private: - struct Entry { - PropertyValue value; - Vertex *vertex; - uint64_t timestamp; - - bool operator<(const Entry &rhs); - bool operator==(const Entry &rhs); - - bool operator<(const PropertyValue &rhs); - bool operator==(const PropertyValue &rhs); - }; - - public: - LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config) - : indices_(indices), constraints_(constraints), config_(config) {} - - /// @throw std::bad_alloc - void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx); - - /// @throw std::bad_alloc - void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx); - - /// @throw std::bad_alloc - bool CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices, - const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info = std::nullopt); - - bool DropIndex(LabelId label, PropertyId property) { return index_.erase({label, property}) > 0; } - - bool IndexExists(LabelId label, PropertyId property) const { return index_.find({label, property}) != index_.end(); } - - std::vector<std::pair<LabelId, PropertyId>> ListIndices() const; - - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); - - class Iterable { - public: - Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower_bound, - const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction, - Indices *indices, Constraints *constraints, Config::Items config); - - class Iterator { - public: - Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator); - - VertexAccessor operator*() const { return current_vertex_accessor_; } - - bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; } - bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; } - - Iterator &operator++(); - - private: - void AdvanceUntilValid(); - - Iterable *self_; - utils::SkipList<Entry>::Iterator index_iterator_; - VertexAccessor current_vertex_accessor_; - Vertex *current_vertex_; - }; - - Iterator begin(); - Iterator end(); - - private: - utils::SkipList<Entry>::Accessor index_accessor_; - LabelId label_; - PropertyId property_; - std::optional<utils::Bound<PropertyValue>> lower_bound_; - std::optional<utils::Bound<PropertyValue>> upper_bound_; - bool bounds_valid_{true}; - View view_; - Transaction *transaction_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; - }; - - Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound, - const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, - Transaction *transaction) { - auto it = index_.find({label, property}); - MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), - property.AsUint()); - return Iterable(it->second.access(), label, property, lower_bound, upper_bound, view, transaction, indices_, - constraints_, config_); - } - - int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { - auto it = index_.find({label, property}); - MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), - property.AsUint()); - return it->second.size(); - } - - /// Supplying a specific value into the count estimation function will return - /// an estimated count of nodes which have their property's value set to - /// `value`. If the `value` specified is `Null`, then an average number of - /// equal elements is returned. - int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const; - - int64_t ApproximateVertexCount(LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower, - const std::optional<utils::Bound<PropertyValue>> &upper) const; - - std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats(); - - std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStats(const storage::LabelId &label); - - void SetIndexStats(const std::pair<storage::LabelId, storage::PropertyId> &key, - const storage::LabelPropertyIndexStats &stats); - - std::optional<storage::LabelPropertyIndexStats> GetIndexStats( - const std::pair<storage::LabelId, storage::PropertyId> &key) const; - - void Clear() { index_.clear(); } - - void RunGC(); - - private: - std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_; - std::map<std::pair<LabelId, PropertyId>, storage::LabelPropertyIndexStats> stats_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; -}; - -struct Indices { - Indices(Constraints *constraints, Config::Items config) - : label_index(this, constraints, config), label_property_index(this, constraints, config) {} - - // Disable copy and move because members hold pointer to `this`. - Indices(const Indices &) = delete; - Indices(Indices &&) = delete; - Indices &operator=(const Indices &) = delete; - Indices &operator=(Indices &&) = delete; - ~Indices() = default; - - LabelIndex label_index; - LabelPropertyIndex label_property_index; -}; - -/// This function should be called from garbage collection to clean-up the -/// index. -void RemoveObsoleteEntries(Indices *indices, uint64_t oldest_active_start_timestamp); - -// Indices are updated whenever an update occurs, instead of only on commit or -// advance command. This is necessary because we want indices to support `NEW` -// view for use in Merge. - -/// This function should be called whenever a label is added to a vertex. -/// @throw std::bad_alloc -void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex, const Transaction &tx); - -/// This function should be called whenever a property is modified on a vertex. -/// @throw std::bad_alloc -void UpdateOnSetProperty(Indices *indices, PropertyId property, const PropertyValue &value, Vertex *vertex, - const Transaction &tx); -} // namespace memgraph::storage diff --git a/src/storage/v2/indices/indices.cpp b/src/storage/v2/indices/indices.cpp new file mode 100644 index 000000000..59915ab18 --- /dev/null +++ b/src/storage/v2/indices/indices.cpp @@ -0,0 +1,38 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/indices/indices.hpp" +#include "storage/v2/inmemory/label_index.hpp" + +namespace memgraph::storage { + +void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const { + static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp); + static_cast<InMemoryLabelPropertyIndex *>(label_property_index_.get()) + ->RemoveObsoleteEntries(oldest_active_start_timestamp); +} + +void Indices::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const { + label_index_->UpdateOnAddLabel(label, vertex, tx); + label_property_index_->UpdateOnAddLabel(label, vertex, tx); +} + +void Indices::UpdateOnRemoveLabel(LabelId label, Vertex *vertex, const Transaction &tx) const { + label_index_->UpdateOnRemoveLabel(label, vertex, tx); + label_property_index_->UpdateOnRemoveLabel(label, vertex, tx); +} + +void Indices::UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) const { + label_property_index_->UpdateOnSetProperty(property, value, vertex, tx); +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/indices/indices.hpp b/src/storage/v2/indices/indices.hpp new file mode 100644 index 000000000..06d0837c4 --- /dev/null +++ b/src/storage/v2/indices/indices.hpp @@ -0,0 +1,68 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <memory> +#include "storage/v2/disk/label_index.hpp" +#include "storage/v2/disk/label_property_index.hpp" +#include "storage/v2/indices/label_index.hpp" +#include "storage/v2/indices/label_property_index.hpp" +#include "storage/v2/inmemory/label_index.hpp" +#include "storage/v2/inmemory/label_property_index.hpp" +#include "storage/v2/storage_mode.hpp" + +namespace memgraph::storage { + +struct Indices { + Indices(Constraints *constraints, const Config &config, StorageMode storage_mode) { + std::invoke([this, constraints, config, storage_mode]() { + if (storage_mode == StorageMode::IN_MEMORY_TRANSACTIONAL || storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { + label_index_ = std::make_unique<InMemoryLabelIndex>(this, constraints, config); + label_property_index_ = std::make_unique<InMemoryLabelPropertyIndex>(this, constraints, config); + } else { + label_index_ = std::make_unique<DiskLabelIndex>(this, constraints, config); + label_property_index_ = std::make_unique<DiskLabelPropertyIndex>(this, constraints, config); + } + }); + } + + Indices(const Indices &) = delete; + Indices(Indices &&) = delete; + Indices &operator=(const Indices &) = delete; + Indices &operator=(Indices &&) = delete; + ~Indices() = default; + + /// This function should be called from garbage collection to clean-up the + /// index. + /// TODO: unused in disk indices + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const; + + // Indices are updated whenever an update occurs, instead of only on commit or + // advance command. This is necessary because we want indices to support `NEW` + // view for use in Merge. + + /// This function should be called whenever a label is added to a vertex. + /// @throw std::bad_alloc + void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const; + + void UpdateOnRemoveLabel(LabelId label, Vertex *vertex, const Transaction &tx) const; + + /// This function should be called whenever a property is modified on a vertex. + /// @throw std::bad_alloc + void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) const; + + std::unique_ptr<LabelIndex> label_index_; + std::unique_ptr<LabelPropertyIndex> label_property_index_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/indices/label_index.hpp b/src/storage/v2/indices/label_index.hpp new file mode 100644 index 000000000..977e4e7a7 --- /dev/null +++ b/src/storage/v2/indices/label_index.hpp @@ -0,0 +1,51 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/vertex.hpp" +#include "storage/v2/vertex_accessor.hpp" + +namespace memgraph::storage { + +class LabelIndex { + public: + LabelIndex(Indices *indices, Constraints *constraints, const Config &config) + : indices_(indices), constraints_(constraints), config_(config) {} + + LabelIndex(const LabelIndex &) = delete; + LabelIndex(LabelIndex &&) = delete; + LabelIndex &operator=(const LabelIndex &) = delete; + LabelIndex &operator=(LabelIndex &&) = delete; + + virtual ~LabelIndex() = default; + + virtual void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) = 0; + + virtual void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_after_update, const Transaction &tx) = 0; + + virtual bool DropIndex(LabelId label) = 0; + + virtual bool IndexExists(LabelId label) const = 0; + + virtual std::vector<LabelId> ListIndices() const = 0; + + virtual uint64_t ApproximateVertexCount(LabelId label) const = 0; + + protected: + /// TODO: andi maybe no need for have those in abstract class if disk storage isn't using it + Indices *indices_; + Constraints *constraints_; + Config config_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/indices/label_property_index.hpp b/src/storage/v2/indices/label_property_index.hpp new file mode 100644 index 000000000..5d9ed90e3 --- /dev/null +++ b/src/storage/v2/indices/label_property_index.hpp @@ -0,0 +1,59 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/vertex.hpp" +#include "storage/v2/vertex_accessor.hpp" + +namespace memgraph::storage { + +class LabelPropertyIndex { + public: + LabelPropertyIndex(Indices *indices, Constraints *constraints, const Config &config) + : indices_(indices), constraints_(constraints), config_(config) {} + + LabelPropertyIndex(const LabelPropertyIndex &) = delete; + LabelPropertyIndex(LabelPropertyIndex &&) = delete; + LabelPropertyIndex &operator=(const LabelPropertyIndex &) = delete; + LabelPropertyIndex &operator=(LabelPropertyIndex &&) = delete; + + virtual ~LabelPropertyIndex() = default; + + virtual void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) = 0; + + virtual void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_after_update, const Transaction &tx) = 0; + + virtual void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) = 0; + + virtual bool DropIndex(LabelId label, PropertyId property) = 0; + + virtual bool IndexExists(LabelId label, PropertyId property) const = 0; + + virtual std::vector<std::pair<LabelId, PropertyId>> ListIndices() const = 0; + + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const = 0; + + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const = 0; + + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const = 0; + + protected: + Indices *indices_; + Constraints *constraints_; + Config config_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/indices_utils.hpp b/src/storage/v2/inmemory/indices_utils.hpp new file mode 100644 index 000000000..dbfc8e80c --- /dev/null +++ b/src/storage/v2/inmemory/indices_utils.hpp @@ -0,0 +1,357 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <thread> +#include "storage/v2/delta.hpp" +#include "storage/v2/mvcc.hpp" +#include "storage/v2/transaction.hpp" +#include "storage/v2/vertex.hpp" +#include "utils/spin_lock.hpp" +#include "utils/synchronized.hpp" + +namespace memgraph::storage { + +using ParalellizedIndexCreationInfo = + std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>; + +/// Traverses deltas visible from transaction with start timestamp greater than +/// the provided timestamp, and calls the provided callback function for each +/// delta. If the callback ever returns true, traversal is stopped and the +/// function returns true. Otherwise, the function returns false. +template <typename TCallback> +inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) { + while (delta != nullptr) { + const auto ts = delta->timestamp->load(std::memory_order_acquire); + // This is a committed change that we see so we shouldn't undo it. + if (ts < timestamp) { + break; + } + if (predicate(*delta)) { + return true; + } + // Move to the next delta. + delta = delta->next.load(std::memory_order_acquire); + } + return false; +} + +/// Helper function for label index garbage collection. Returns true if there's +/// a reachable version of the vertex that has the given label. +inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp) { + bool has_label{false}; + bool deleted{false}; + const Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + has_label = utils::Contains(vertex.labels, label); + deleted = vertex.deleted; + delta = vertex.delta; + } + if (!deleted && has_label) { + return true; + } + return AnyVersionSatisfiesPredicate(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_LABEL: + if (delta.label == label) { + MG_ASSERT(!has_label, "Invalid database state!"); + has_label = true; + } + break; + case Delta::Action::REMOVE_LABEL: + if (delta.label == label) { + MG_ASSERT(has_label, "Invalid database state!"); + has_label = false; + } + break; + case Delta::Action::RECREATE_OBJECT: { + MG_ASSERT(deleted, "Invalid database state!"); + deleted = false; + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + MG_ASSERT(!deleted, "Invalid database state!"); + deleted = true; + break; + } + 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; + } + return !deleted && has_label; + }); +} + +/// Helper function for label-property index garbage collection. Returns true if +/// there's a reachable version of the vertex that has the given label and +/// property value. +inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, const PropertyValue &value, + uint64_t timestamp) { + bool has_label{false}; + bool current_value_equal_to_value{value.IsNull()}; + bool deleted{false}; + const Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + has_label = utils::Contains(vertex.labels, label); + current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); + deleted = vertex.deleted; + delta = vertex.delta; + } + + if (!deleted && has_label && current_value_equal_to_value) { + return true; + } + + return AnyVersionSatisfiesPredicate( + timestamp, delta, [&has_label, ¤t_value_equal_to_value, &deleted, label, key, &value](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_LABEL: + if (delta.label == label) { + MG_ASSERT(!has_label, "Invalid database state!"); + has_label = true; + } + break; + case Delta::Action::REMOVE_LABEL: + if (delta.label == label) { + MG_ASSERT(has_label, "Invalid database state!"); + has_label = false; + } + break; + case Delta::Action::SET_PROPERTY: + if (delta.property.key == key) { + current_value_equal_to_value = delta.property.value == value; + } + break; + case Delta::Action::RECREATE_OBJECT: { + MG_ASSERT(deleted, "Invalid database state!"); + deleted = false; + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + MG_ASSERT(!deleted, "Invalid database state!"); + deleted = true; + break; + } + 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; + } + return !deleted && has_label && current_value_equal_to_value; + }); +} + +// Helper function for iterating through label index. Returns true if this +// transaction can see the given vertex, and the visible version has the given +// label. +inline bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *transaction, View view) { + bool deleted = false; + bool has_label = false; + const Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + deleted = vertex.deleted; + has_label = utils::Contains(vertex.labels, label); + delta = vertex.delta; + } + ApplyDeltasForRead(transaction, delta, view, [&deleted, &has_label, label](const Delta &delta) { + switch (delta.action) { + case Delta::Action::REMOVE_LABEL: { + if (delta.label == label) { + MG_ASSERT(has_label, "Invalid database state!"); + has_label = false; + } + break; + } + case Delta::Action::ADD_LABEL: { + if (delta.label == label) { + MG_ASSERT(!has_label, "Invalid database state!"); + has_label = true; + } + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + MG_ASSERT(!deleted, "Invalid database state!"); + deleted = true; + break; + } + case Delta::Action::RECREATE_OBJECT: { + MG_ASSERT(deleted, "Invalid database state!"); + deleted = false; + break; + } + 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; + } + }); + return !deleted && has_label; +} + +// Helper function for iterating through label-property index. Returns true if +// this transaction can see the given vertex, and the visible version has the +// given label and property. +inline bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, + const PropertyValue &value, Transaction *transaction, View view) { + bool deleted = false; + bool has_label = false; + bool current_value_equal_to_value = value.IsNull(); + const Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex.lock); + deleted = vertex.deleted; + has_label = utils::Contains(vertex.labels, label); + current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); + delta = vertex.delta; + } + ApplyDeltasForRead(transaction, delta, view, + [&deleted, &has_label, ¤t_value_equal_to_value, key, label, &value](const Delta &delta) { + switch (delta.action) { + case Delta::Action::SET_PROPERTY: { + if (delta.property.key == key) { + current_value_equal_to_value = delta.property.value == value; + } + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + MG_ASSERT(!deleted, "Invalid database state!"); + deleted = true; + break; + } + case Delta::Action::RECREATE_OBJECT: { + MG_ASSERT(deleted, "Invalid database state!"); + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + if (delta.label == label) { + MG_ASSERT(!has_label, "Invalid database state!"); + has_label = true; + } + break; + case Delta::Action::REMOVE_LABEL: + if (delta.label == label) { + MG_ASSERT(has_label, "Invalid database state!"); + has_label = false; + } + break; + 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; + } + }); + return !deleted && has_label && current_value_equal_to_value; +} + +template <typename TIndexAccessor> +inline void TryInsertLabelIndex(Vertex &vertex, LabelId label, TIndexAccessor &index_accessor) { + if (vertex.deleted || !utils::Contains(vertex.labels, label)) { + return; + } + + index_accessor.insert({&vertex, 0}); +} + +template <typename TIndexAccessor> +inline void TryInsertLabelPropertyIndex(Vertex &vertex, std::pair<LabelId, PropertyId> label_property_pair, + TIndexAccessor &index_accessor) { + if (vertex.deleted || !utils::Contains(vertex.labels, label_property_pair.first)) { + return; + } + auto value = vertex.properties.GetProperty(label_property_pair.second); + if (value.IsNull()) { + return; + } + index_accessor.insert({std::move(value), &vertex, 0}); +} + +template <typename TSkiplistIter, typename TIndex, typename TIndexKey, typename TFunc> +inline void CreateIndexOnSingleThread(utils::SkipList<Vertex>::Accessor &vertices, TSkiplistIter it, TIndex &index, + TIndexKey key, const TFunc &func) { + utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; + try { + auto acc = it->second.access(); + for (Vertex &vertex : vertices) { + func(vertex, key, acc); + } + } catch (const utils::OutOfMemoryException &) { + utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_exception_blocker; + index.erase(it); + throw; + } +} + +template <typename TIndex, typename TIndexKey, typename TSKiplistIter, typename TFunc> +inline void CreateIndexOnMultipleThreads(utils::SkipList<Vertex>::Accessor &vertices, TSKiplistIter skiplist_iter, + TIndex &index, TIndexKey key, + const ParalellizedIndexCreationInfo ¶lell_exec_info, const TFunc &func) { + utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; + + const auto &vertex_batches = paralell_exec_info.first; + const auto thread_count = std::min(paralell_exec_info.second, vertex_batches.size()); + + MG_ASSERT(!vertex_batches.empty(), + "The size of batches should always be greater than zero if you want to use the parallel version of index " + "creation!"); + + std::atomic<uint64_t> batch_counter = 0; + + utils::Synchronized<std::optional<utils::OutOfMemoryException>, utils::SpinLock> maybe_error{}; + { + std::vector<std::jthread> threads; + threads.reserve(thread_count); + + for (auto i{0U}; i < thread_count; ++i) { + threads.emplace_back( + [&skiplist_iter, &func, &index, &vertex_batches, &maybe_error, &batch_counter, &key, &vertices]() { + while (!maybe_error.Lock()->has_value()) { + const auto batch_index = batch_counter++; + if (batch_index >= vertex_batches.size()) { + return; + } + const auto &batch = vertex_batches[batch_index]; + auto index_accessor = index.at(key).access(); + auto it = vertices.find(batch.first); + + try { + for (auto i{0U}; i < batch.second; ++i, ++it) { + func(*it, key, index_accessor); + } + + } catch (utils::OutOfMemoryException &failure) { + utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_exception_blocker; + index.erase(skiplist_iter); + *maybe_error.Lock() = std::move(failure); + } + } + }); + } + } + if (maybe_error.Lock()->has_value()) { + throw utils::OutOfMemoryException((*maybe_error.Lock())->what()); + } +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/label_index.cpp b/src/storage/v2/inmemory/label_index.cpp new file mode 100644 index 000000000..625590f5e --- /dev/null +++ b/src/storage/v2/inmemory/label_index.cpp @@ -0,0 +1,192 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/inmemory/label_index.hpp" +#include "storage/v2/inmemory/indices_utils.hpp" + +namespace memgraph::storage { + +InMemoryLabelIndex::InMemoryLabelIndex(Indices *indices, Constraints *constraints, Config config) + : LabelIndex(indices, constraints, config) {} + +void InMemoryLabelIndex::UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) { + auto it = index_.find(added_label); + if (it == index_.end()) return; + auto acc = it->second.access(); + acc.insert(Entry{vertex_after_update, tx.start_timestamp}); +} + +bool InMemoryLabelIndex::CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices, + const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info) { + const auto create_index_seq = [this](LabelId label, utils::SkipList<Vertex>::Accessor &vertices, + std::map<LabelId, utils::SkipList<Entry>>::iterator it) { + using IndexAccessor = decltype(it->second.access()); + + CreateIndexOnSingleThread(vertices, it, index_, label, + [](Vertex &vertex, LabelId label, IndexAccessor &index_accessor) { + TryInsertLabelIndex(vertex, label, index_accessor); + }); + + return true; + }; + + const auto create_index_par = [this](LabelId label, utils::SkipList<Vertex>::Accessor &vertices, + std::map<LabelId, utils::SkipList<Entry>>::iterator label_it, + const ParalellizedIndexCreationInfo ¶lell_exec_info) { + using IndexAccessor = decltype(label_it->second.access()); + + CreateIndexOnMultipleThreads(vertices, label_it, index_, label, paralell_exec_info, + [](Vertex &vertex, LabelId label, IndexAccessor &index_accessor) { + TryInsertLabelIndex(vertex, label, index_accessor); + }); + + return true; + }; + + auto [it, emplaced] = index_.emplace(std::piecewise_construct, std::forward_as_tuple(label), std::forward_as_tuple()); + if (!emplaced) { + // Index already exists. + return false; + } + + if (paralell_exec_info) { + return create_index_par(label, vertices, it, *paralell_exec_info); + } + return create_index_seq(label, vertices, it); +} + +bool InMemoryLabelIndex::DropIndex(LabelId label) { return index_.erase(label) > 0; } + +bool InMemoryLabelIndex::IndexExists(LabelId label) const { return index_.find(label) != index_.end(); } + +std::vector<LabelId> InMemoryLabelIndex::ListIndices() const { + std::vector<LabelId> ret; + ret.reserve(index_.size()); + for (const auto &item : index_) { + ret.push_back(item.first); + } + return ret; +} + +void InMemoryLabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { + for (auto &label_storage : index_) { + auto vertices_acc = label_storage.second.access(); + for (auto it = vertices_acc.begin(); it != vertices_acc.end();) { + auto next_it = it; + ++next_it; + + if (it->timestamp >= oldest_active_start_timestamp) { + it = next_it; + continue; + } + + if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) || + !AnyVersionHasLabel(*it->vertex, label_storage.first, oldest_active_start_timestamp)) { + vertices_acc.remove(*it); + } + + it = next_it; + } + } +} + +InMemoryLabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, + Transaction *transaction, Indices *indices, Constraints *constraints, + const Config &config) + : index_accessor_(std::move(index_accessor)), + label_(label), + view_(view), + transaction_(transaction), + indices_(indices), + constraints_(constraints), + config_(config) {} + +InMemoryLabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator) + : self_(self), + index_iterator_(index_iterator), + current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_.items), + current_vertex_(nullptr) { + AdvanceUntilValid(); +} + +InMemoryLabelIndex::Iterable::Iterator &InMemoryLabelIndex::Iterable::Iterator::operator++() { + ++index_iterator_; + AdvanceUntilValid(); + return *this; +} + +void InMemoryLabelIndex::Iterable::Iterator::AdvanceUntilValid() { + for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) { + if (index_iterator_->vertex == current_vertex_) { + continue; + } + if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) { + current_vertex_ = index_iterator_->vertex; + current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, + self_->constraints_, self_->config_.items}; + break; + } + } +} + +uint64_t InMemoryLabelIndex::ApproximateVertexCount(LabelId label) const { + auto it = index_.find(label); + MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint()); + return it->second.size(); +} + +void InMemoryLabelIndex::RunGC() { + for (auto &index_entry : index_) { + index_entry.second.run_gc(); + } +} + +InMemoryLabelIndex::Iterable InMemoryLabelIndex::Vertices(LabelId label, View view, Transaction *transaction) { + const auto it = index_.find(label); + MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint()); + return {it->second.access(), label, view, transaction, indices_, constraints_, config_}; +} + +void InMemoryLabelIndex::SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats) { + stats_[label] = stats; +} + +std::optional<LabelIndexStats> InMemoryLabelIndex::GetIndexStats(const storage::LabelId &label) const { + if (auto it = stats_.find(label); it != stats_.end()) { + return it->second; + } + return {}; +} + +std::vector<LabelId> InMemoryLabelIndex::ClearIndexStats() { + std::vector<LabelId> deleted_indexes; + deleted_indexes.reserve(stats_.size()); + std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes), + [](const auto &elem) { return elem.first; }); + stats_.clear(); + return deleted_indexes; +} + +std::vector<LabelId> InMemoryLabelIndex::DeleteIndexStats(const storage::LabelId &label) { + std::vector<LabelId> deleted_indexes; + for (auto it = stats_.cbegin(); it != stats_.cend();) { + if (it->first == label) { + deleted_indexes.push_back(it->first); + it = stats_.erase(it); + } else { + ++it; + } + } + + return deleted_indexes; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/label_index.hpp b/src/storage/v2/inmemory/label_index.hpp new file mode 100644 index 000000000..4a32640f6 --- /dev/null +++ b/src/storage/v2/inmemory/label_index.hpp @@ -0,0 +1,117 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/indices/label_index.hpp" +#include "storage/v2/vertex.hpp" + +namespace memgraph::storage { + +struct LabelIndexStats { + uint64_t count; + double avg_degree; +}; + +using ParalellizedIndexCreationInfo = + std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>; + +class InMemoryLabelIndex : public storage::LabelIndex { + private: + struct Entry { + Vertex *vertex; + uint64_t timestamp; + + bool operator<(const Entry &rhs) { + return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp); + } + bool operator==(const Entry &rhs) const { return vertex == rhs.vertex && timestamp == rhs.timestamp; } + }; + + public: + InMemoryLabelIndex(Indices *indices, Constraints *constraints, Config config); + + /// @throw std::bad_alloc + void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) override; + + void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_before_update, const Transaction &tx) override {} + + /// @throw std::bad_alloc + bool CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices, + const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info); + + /// Returns false if there was no index to drop + bool DropIndex(LabelId label) override; + + bool IndexExists(LabelId label) const override; + + std::vector<LabelId> ListIndices() const override; + + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + + class Iterable { + public: + Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction, + Indices *indices, Constraints *constraints, const Config &config); + + class Iterator { + public: + Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator); + + VertexAccessor operator*() const { return current_vertex_accessor_; } + + bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; } + bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; } + + Iterator &operator++(); + + private: + void AdvanceUntilValid(); + + Iterable *self_; + utils::SkipList<Entry>::Iterator index_iterator_; + VertexAccessor current_vertex_accessor_; + Vertex *current_vertex_; + }; + + Iterator begin() { return {this, index_accessor_.begin()}; } + Iterator end() { return {this, index_accessor_.end()}; } + + private: + utils::SkipList<Entry>::Accessor index_accessor_; + LabelId label_; + View view_; + Transaction *transaction_; + Indices *indices_; + Constraints *constraints_; + Config config_; + }; + + uint64_t ApproximateVertexCount(LabelId label) const override; + + void RunGC(); + + Iterable Vertices(LabelId label, View view, Transaction *transaction); + + void SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats); + + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const; + + std::vector<LabelId> ClearIndexStats(); + + std::vector<LabelId> DeleteIndexStats(const storage::LabelId &label); + + private: + std::map<LabelId, utils::SkipList<Entry>> index_; + std::map<LabelId, storage::LabelIndexStats> stats_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/label_property_index.cpp b/src/storage/v2/inmemory/label_property_index.cpp new file mode 100644 index 000000000..b19e316f0 --- /dev/null +++ b/src/storage/v2/inmemory/label_property_index.cpp @@ -0,0 +1,424 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/inmemory/label_property_index.hpp" +#include "storage/v2/inmemory/indices_utils.hpp" + +namespace memgraph::storage { + +bool InMemoryLabelPropertyIndex::Entry::operator<(const Entry &rhs) const { + if (value < rhs.value) { + return true; + } + if (rhs.value < value) { + return false; + } + return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp); +} + +bool InMemoryLabelPropertyIndex::Entry::operator==(const Entry &rhs) const { + return value == rhs.value && vertex == rhs.vertex && timestamp == rhs.timestamp; +} + +bool InMemoryLabelPropertyIndex::Entry::operator<(const PropertyValue &rhs) const { return value < rhs; } + +bool InMemoryLabelPropertyIndex::Entry::operator==(const PropertyValue &rhs) const { return value == rhs; } + +InMemoryLabelPropertyIndex::InMemoryLabelPropertyIndex(Indices *indices, Constraints *constraints, const Config &config) + : LabelPropertyIndex(indices, constraints, config) {} + +bool InMemoryLabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, + utils::SkipList<Vertex>::Accessor vertices, + const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info) { + auto create_index_seq = [this](LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor &vertices, + std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>>::iterator it) { + using IndexAccessor = decltype(it->second.access()); + + CreateIndexOnSingleThread(vertices, it, index_, std::make_pair(label, property), + [](Vertex &vertex, std::pair<LabelId, PropertyId> key, IndexAccessor &index_accessor) { + TryInsertLabelPropertyIndex(vertex, key, index_accessor); + }); + + return true; + }; + + auto create_index_par = + [this](LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor &vertices, + std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>>::iterator label_property_it, + const ParalellizedIndexCreationInfo ¶lell_exec_info) { + using IndexAccessor = decltype(label_property_it->second.access()); + + CreateIndexOnMultipleThreads( + vertices, label_property_it, index_, std::make_pair(label, property), paralell_exec_info, + [](Vertex &vertex, std::pair<LabelId, PropertyId> key, IndexAccessor &index_accessor) { + TryInsertLabelPropertyIndex(vertex, key, index_accessor); + }); + + return true; + }; + + auto [it, emplaced] = + index_.emplace(std::piecewise_construct, std::forward_as_tuple(label, property), std::forward_as_tuple()); + if (!emplaced) { + // Index already exists. + return false; + } + + if (paralell_exec_info) { + return create_index_par(label, property, vertices, it, *paralell_exec_info); + } + return create_index_seq(label, property, vertices, it); +} + +void InMemoryLabelPropertyIndex::UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, + const Transaction &tx) { + for (auto &[label_prop, storage] : index_) { + if (label_prop.first != added_label) { + continue; + } + auto prop_value = vertex_after_update->properties.GetProperty(label_prop.second); + if (!prop_value.IsNull()) { + auto acc = storage.access(); + acc.insert(Entry{std::move(prop_value), vertex_after_update, tx.start_timestamp}); + } + } +} + +void InMemoryLabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) { + if (value.IsNull()) { + return; + } + for (auto &[label_prop, storage] : index_) { + if (label_prop.second != property) { + continue; + } + if (utils::Contains(vertex->labels, label_prop.first)) { + auto acc = storage.access(); + acc.insert(Entry{value, vertex, tx.start_timestamp}); + } + } +} + +bool InMemoryLabelPropertyIndex::DropIndex(LabelId label, PropertyId property) { + return index_.erase({label, property}) > 0; +} + +bool InMemoryLabelPropertyIndex::IndexExists(LabelId label, PropertyId property) const { + return index_.find({label, property}) != index_.end(); +} + +std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::ListIndices() const { + std::vector<std::pair<LabelId, PropertyId>> ret; + ret.reserve(index_.size()); + for (const auto &item : index_) { + ret.push_back(item.first); + } + return ret; +} + +void InMemoryLabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { + for (auto &[label_property, index] : index_) { + auto index_acc = index.access(); + for (auto it = index_acc.begin(); it != index_acc.end();) { + auto next_it = it; + ++next_it; + + if (it->timestamp >= oldest_active_start_timestamp) { + it = next_it; + continue; + } + + if ((next_it != index_acc.end() && it->vertex == next_it->vertex && it->value == next_it->value) || + !AnyVersionHasLabelProperty(*it->vertex, label_property.first, label_property.second, it->value, + oldest_active_start_timestamp)) { + index_acc.remove(*it); + } + it = next_it; + } + } +} + +InMemoryLabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, + utils::SkipList<Entry>::Iterator index_iterator) + : self_(self), + index_iterator_(index_iterator), + current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_.items), + current_vertex_(nullptr) { + AdvanceUntilValid(); +} + +InMemoryLabelPropertyIndex::Iterable::Iterator &InMemoryLabelPropertyIndex::Iterable::Iterator::operator++() { + ++index_iterator_; + AdvanceUntilValid(); + return *this; +} + +void InMemoryLabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() { + for (; index_iterator_ != self_->index_accessor_.end(); ++index_iterator_) { + if (index_iterator_->vertex == current_vertex_) { + continue; + } + + if (self_->lower_bound_) { + if (index_iterator_->value < self_->lower_bound_->value()) { + continue; + } + if (!self_->lower_bound_->IsInclusive() && index_iterator_->value == self_->lower_bound_->value()) { + continue; + } + } + if (self_->upper_bound_) { + if (self_->upper_bound_->value() < index_iterator_->value) { + index_iterator_ = self_->index_accessor_.end(); + break; + } + if (!self_->upper_bound_->IsInclusive() && index_iterator_->value == self_->upper_bound_->value()) { + index_iterator_ = self_->index_accessor_.end(); + break; + } + } + + if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_, + index_iterator_->value, self_->transaction_, self_->view_)) { + current_vertex_ = index_iterator_->vertex; + current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, + self_->constraints_, self_->config_.items); + break; + } + } +} + +// These constants represent the smallest possible value of each type that is +// contained in a `PropertyValue`. Note that numbers (integers and doubles) are +// treated as the same "type" in `PropertyValue`. +const PropertyValue kSmallestBool = PropertyValue(false); +// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) +static_assert(-std::numeric_limits<double>::infinity() < std::numeric_limits<int64_t>::min()); +const PropertyValue kSmallestNumber = PropertyValue(-std::numeric_limits<double>::infinity()); +const PropertyValue kSmallestString = PropertyValue(""); +const PropertyValue kSmallestList = PropertyValue(std::vector<PropertyValue>()); +const PropertyValue kSmallestMap = PropertyValue(std::map<std::string, PropertyValue>()); +const PropertyValue kSmallestTemporalData = + PropertyValue(TemporalData{static_cast<TemporalType>(0), std::numeric_limits<int64_t>::min()}); + +InMemoryLabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, + PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, + Transaction *transaction, Indices *indices, Constraints *constraints, + const Config &config) + : index_accessor_(std::move(index_accessor)), + label_(label), + property_(property), + lower_bound_(lower_bound), + upper_bound_(upper_bound), + view_(view), + transaction_(transaction), + indices_(indices), + constraints_(constraints), + config_(config) { + // We have to fix the bounds that the user provided to us. If the user + // provided only one bound we should make sure that only values of that type + // are returned by the iterator. We ensure this by supplying either an + // inclusive lower bound of the same type, or an exclusive upper bound of the + // following type. If neither bound is set we yield all items in the index. + + // First we statically verify that our assumptions about the `PropertyValue` + // type ordering holds. + static_assert(PropertyValue::Type::Bool < PropertyValue::Type::Int); + static_assert(PropertyValue::Type::Int < PropertyValue::Type::Double); + static_assert(PropertyValue::Type::Double < PropertyValue::Type::String); + static_assert(PropertyValue::Type::String < PropertyValue::Type::List); + static_assert(PropertyValue::Type::List < PropertyValue::Type::Map); + + // Remove any bounds that are set to `Null` because that isn't a valid value. + if (lower_bound_ && lower_bound_->value().IsNull()) { + lower_bound_ = std::nullopt; + } + if (upper_bound_ && upper_bound_->value().IsNull()) { + upper_bound_ = std::nullopt; + } + + // Check whether the bounds are of comparable types if both are supplied. + if (lower_bound_ && upper_bound_ && + !PropertyValue::AreComparableTypes(lower_bound_->value().type(), upper_bound_->value().type())) { + bounds_valid_ = false; + return; + } + + // Set missing bounds. + if (lower_bound_ && !upper_bound_) { + // Here we need to supply an upper bound. The upper bound is set to an + // exclusive lower bound of the following type. + switch (lower_bound_->value().type()) { + case PropertyValue::Type::Null: + // This shouldn't happen because of the nullopt-ing above. + LOG_FATAL("Invalid database state!"); + break; + case PropertyValue::Type::Bool: + upper_bound_ = utils::MakeBoundExclusive(kSmallestNumber); + break; + case PropertyValue::Type::Int: + case PropertyValue::Type::Double: + // Both integers and doubles are treated as the same type in + // `PropertyValue` and they are interleaved when sorted. + upper_bound_ = utils::MakeBoundExclusive(kSmallestString); + break; + case PropertyValue::Type::String: + upper_bound_ = utils::MakeBoundExclusive(kSmallestList); + break; + case PropertyValue::Type::List: + upper_bound_ = utils::MakeBoundExclusive(kSmallestMap); + break; + case PropertyValue::Type::Map: + upper_bound_ = utils::MakeBoundExclusive(kSmallestTemporalData); + break; + case PropertyValue::Type::TemporalData: + // This is the last type in the order so we leave the upper bound empty. + break; + } + } + if (upper_bound_ && !lower_bound_) { + // Here we need to supply a lower bound. The lower bound is set to an + // inclusive lower bound of the current type. + switch (upper_bound_->value().type()) { + case PropertyValue::Type::Null: + // This shouldn't happen because of the nullopt-ing above. + LOG_FATAL("Invalid database state!"); + break; + case PropertyValue::Type::Bool: + lower_bound_ = utils::MakeBoundInclusive(kSmallestBool); + break; + case PropertyValue::Type::Int: + case PropertyValue::Type::Double: + // Both integers and doubles are treated as the same type in + // `PropertyValue` and they are interleaved when sorted. + lower_bound_ = utils::MakeBoundInclusive(kSmallestNumber); + break; + case PropertyValue::Type::String: + lower_bound_ = utils::MakeBoundInclusive(kSmallestString); + break; + case PropertyValue::Type::List: + lower_bound_ = utils::MakeBoundInclusive(kSmallestList); + break; + case PropertyValue::Type::Map: + lower_bound_ = utils::MakeBoundInclusive(kSmallestMap); + break; + case PropertyValue::Type::TemporalData: + lower_bound_ = utils::MakeBoundInclusive(kSmallestTemporalData); + break; + } + } +} + +InMemoryLabelPropertyIndex::Iterable::Iterator InMemoryLabelPropertyIndex::Iterable::begin() { + // If the bounds are set and don't have comparable types we don't yield any + // items from the index. + if (!bounds_valid_) return {this, index_accessor_.end()}; + auto index_iterator = index_accessor_.begin(); + if (lower_bound_) { + index_iterator = index_accessor_.find_equal_or_greater(lower_bound_->value()); + } + return {this, index_iterator}; +} + +InMemoryLabelPropertyIndex::Iterable::Iterator InMemoryLabelPropertyIndex::Iterable::end() { + return {this, index_accessor_.end()}; +} + +uint64_t InMemoryLabelPropertyIndex::ApproximateVertexCount(LabelId label, PropertyId property) const { + auto it = index_.find({label, property}); + MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); + return it->second.size(); +} + +uint64_t InMemoryLabelPropertyIndex::ApproximateVertexCount(LabelId label, PropertyId property, + const PropertyValue &value) const { + auto it = index_.find({label, property}); + MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); + auto acc = it->second.access(); + if (!value.IsNull()) { + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return acc.estimate_count(value, utils::SkipListLayerForCountEstimation(acc.size())); + } + // The value `Null` won't ever appear in the index because it indicates that + // the property shouldn't exist. Instead, this value is used as an indicator + // to estimate the average number of equal elements in the list (for any + // given value). + return acc.estimate_average_number_of_equals( + [](const auto &first, const auto &second) { return first.value == second.value; }, + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + utils::SkipListLayerForAverageEqualsEstimation(acc.size())); +} + +uint64_t InMemoryLabelPropertyIndex::ApproximateVertexCount( + LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const { + auto it = index_.find({label, property}); + MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); + auto acc = it->second.access(); + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return acc.estimate_range_count(lower, upper, utils::SkipListLayerForCountEstimation(acc.size())); +} + +std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::ClearIndexStats() { + std::vector<std::pair<LabelId, PropertyId>> deleted_indexes; + deleted_indexes.reserve(stats_.size()); + std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes), + [](const auto &elem) { return elem.first; }); + stats_.clear(); + return deleted_indexes; +} + +std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::DeleteIndexStats( + const storage::LabelId &label) { + std::vector<std::pair<LabelId, PropertyId>> deleted_indexes; + for (auto it = stats_.cbegin(); it != stats_.cend();) { + if (it->first.first == label) { + deleted_indexes.push_back(it->first); + it = stats_.erase(it); + } else { + ++it; + } + } + return deleted_indexes; +} + +void InMemoryLabelPropertyIndex::SetIndexStats(const std::pair<storage::LabelId, storage::PropertyId> &key, + const LabelPropertyIndexStats &stats) { + stats_[key] = stats; +} + +std::optional<LabelPropertyIndexStats> InMemoryLabelPropertyIndex::GetIndexStats( + const std::pair<storage::LabelId, storage::PropertyId> &key) const { + if (auto it = stats_.find(key); it != stats_.end()) { + return it->second; + } + return {}; +} + +void InMemoryLabelPropertyIndex::RunGC() { + for (auto &index_entry : index_) { + index_entry.second.run_gc(); + } +} + +InMemoryLabelPropertyIndex::Iterable InMemoryLabelPropertyIndex::Vertices( + LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction) { + auto it = index_.find({label, property}); + MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); + return {it->second.access(), label, property, lower_bound, upper_bound, view, + transaction, indices_, constraints_, config_}; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/label_property_index.hpp b/src/storage/v2/inmemory/label_property_index.hpp new file mode 100644 index 000000000..13db8109e --- /dev/null +++ b/src/storage/v2/inmemory/label_property_index.hpp @@ -0,0 +1,141 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/indices/label_property_index.hpp" + +namespace memgraph::storage { + +struct LabelPropertyIndexStats { + uint64_t count, distinct_values_count; + double statistic, avg_group_size, avg_degree; +}; + +/// TODO: andi. Too many copies, extract at one place +using ParalellizedIndexCreationInfo = + std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>; + +class InMemoryLabelPropertyIndex : public storage::LabelPropertyIndex { + private: + struct Entry { + PropertyValue value; + Vertex *vertex; + uint64_t timestamp; + + bool operator<(const Entry &rhs) const; + bool operator==(const Entry &rhs) const; + + bool operator<(const PropertyValue &rhs) const; + bool operator==(const PropertyValue &rhs) const; + }; + + public: + InMemoryLabelPropertyIndex(Indices *indices, Constraints *constraints, const Config &config); + + /// @throw std::bad_alloc + bool CreateIndex(LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices, + const std::optional<ParalellizedIndexCreationInfo> ¶lell_exec_info); + + /// @throw std::bad_alloc + void UpdateOnAddLabel(LabelId added_label, Vertex *vertex_after_update, const Transaction &tx) override; + + void UpdateOnRemoveLabel(LabelId removed_label, Vertex *vertex_before_update, const Transaction &tx) override {} + + /// @throw std::bad_alloc + void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, + const Transaction &tx) override; + + bool DropIndex(LabelId label, PropertyId property) override; + + bool IndexExists(LabelId label, PropertyId property) const override; + + std::vector<std::pair<LabelId, PropertyId>> ListIndices() const override; + + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + + class Iterable { + public: + Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction, + Indices *indices, Constraints *constraints, const Config &config); + + class Iterator { + public: + Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator); + + VertexAccessor operator*() const { return current_vertex_accessor_; } + + bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; } + bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; } + + Iterator &operator++(); + + private: + void AdvanceUntilValid(); + + Iterable *self_; + utils::SkipList<Entry>::Iterator index_iterator_; + VertexAccessor current_vertex_accessor_; + Vertex *current_vertex_; + }; + + Iterator begin(); + Iterator end(); + + private: + utils::SkipList<Entry>::Accessor index_accessor_; + LabelId label_; + PropertyId property_; + std::optional<utils::Bound<PropertyValue>> lower_bound_; + std::optional<utils::Bound<PropertyValue>> upper_bound_; + bool bounds_valid_{true}; + View view_; + Transaction *transaction_; + Indices *indices_; + Constraints *constraints_; + Config config_; + }; + + uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override; + + /// Supplying a specific value into the count estimation function will return + /// an estimated count of nodes which have their property's value set to + /// `value`. If the `value` specified is `Null`, then an average number of + /// equal elements is returned. + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override; + + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const override; + + std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats(); + + std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStats(const storage::LabelId &label); + + void SetIndexStats(const std::pair<storage::LabelId, storage::PropertyId> &key, + const storage::LabelPropertyIndexStats &stats); + + std::optional<storage::LabelPropertyIndexStats> GetIndexStats( + const std::pair<storage::LabelId, storage::PropertyId> &key) const; + + void RunGC(); + + Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction); + + private: + std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_; + std::map<std::pair<LabelId, PropertyId>, storage::LabelPropertyIndexStats> stats_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp new file mode 100644 index 000000000..159fff10c --- /dev/null +++ b/src/storage/v2/inmemory/storage.cpp @@ -0,0 +1,2075 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/inmemory/storage.hpp" +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/durability/durability.hpp" +#include "storage/v2/durability/snapshot.hpp" +#include "storage/v2/durability/wal.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/storage_mode.hpp" +#include "storage/v2/vertex_accessor.hpp" +#include "utils/stat.hpp" + +/// REPLICATION /// +#include "storage/v2/replication/replication_client.hpp" +#include "storage/v2/replication/replication_server.hpp" +#include "storage/v2/replication/rpc.hpp" +#include "storage/v2/storage_error.hpp" + +namespace memgraph::storage { + +using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; + +namespace { +inline constexpr uint16_t kEpochHistoryRetention = 1000; + +std::string RegisterReplicaErrorToString(InMemoryStorage::RegisterReplicaError error) { + switch (error) { + case InMemoryStorage::RegisterReplicaError::NAME_EXISTS: + return "NAME_EXISTS"; + case InMemoryStorage::RegisterReplicaError::END_POINT_EXISTS: + return "END_POINT_EXISTS"; + case InMemoryStorage::RegisterReplicaError::CONNECTION_FAILED: + return "CONNECTION_FAILED"; + case InMemoryStorage::RegisterReplicaError::COULD_NOT_BE_PERSISTED: + return "COULD_NOT_BE_PERSISTED"; + } +} +} // namespace + +InMemoryStorage::InMemoryStorage(Config config) + : Storage(config, StorageMode::IN_MEMORY_TRANSACTIONAL), + snapshot_directory_(config.durability.storage_directory / durability::kSnapshotDirectory), + lock_file_path_(config.durability.storage_directory / durability::kLockFile), + wal_directory_(config.durability.storage_directory / durability::kWalDirectory), + uuid_(utils::GenerateUUID()), + epoch_id_(utils::GenerateUUID()), + global_locker_(file_retainer_.AddLocker()) { + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || + config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) { + // Create the directory initially to crash the database in case of + // permission errors. This is done early to crash the database on startup + // instead of crashing the database for the first time during runtime (which + // could be an unpleasant surprise). + utils::EnsureDirOrDie(snapshot_directory_); + // Same reasoning as above. + utils::EnsureDirOrDie(wal_directory_); + + // Verify that the user that started the process is the same user that is + // the owner of the storage directory. + durability::VerifyStorageDirectoryOwnerAndProcessUserOrDie(config_.durability.storage_directory); + + // Create the lock file and open a handle to it. This will crash the + // database if it can't open the file for writing or if any other process is + // holding the file opened. + lock_file_handle_.Open(lock_file_path_, utils::OutputFile::Mode::OVERWRITE_EXISTING); + MG_ASSERT(lock_file_handle_.AcquireLock(), + "Couldn't acquire lock on the storage directory {}" + "!\nAnother Memgraph process is currently running with the same " + "storage directory, please stop it first before starting this " + "process!", + config_.durability.storage_directory); + } + if (config_.durability.recover_on_startup) { + auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, + &vertices_, &edges_, &edge_count_, name_id_mapper_.get(), &indices_, + &constraints_, config_, &wal_seq_num_); + if (info) { + vertex_id_ = info->next_vertex_id; + edge_id_ = info->next_edge_id; + timestamp_ = std::max(timestamp_, info->next_timestamp); + if (info->last_commit_timestamp) { + last_commit_timestamp_ = *info->last_commit_timestamp; + } + } + } else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || + config_.durability.snapshot_on_exit) { + bool files_moved = false; + auto backup_root = config_.durability.storage_directory / durability::kBackupDirectory; + for (const auto &[path, dirname, what] : + {std::make_tuple(snapshot_directory_, durability::kSnapshotDirectory, "snapshot"), + std::make_tuple(wal_directory_, durability::kWalDirectory, "WAL")}) { + if (!utils::DirExists(path)) continue; + auto backup_curr = backup_root / dirname; + std::error_code error_code; + for (const auto &item : std::filesystem::directory_iterator(path, error_code)) { + utils::EnsureDirOrDie(backup_root); + utils::EnsureDirOrDie(backup_curr); + std::error_code item_error_code; + std::filesystem::rename(item.path(), backup_curr / item.path().filename(), item_error_code); + MG_ASSERT(!item_error_code, "Couldn't move {} file {} because of: {}", what, item.path(), + item_error_code.message()); + files_moved = true; + } + MG_ASSERT(!error_code, "Couldn't backup {} files because of: {}", what, error_code.message()); + } + if (files_moved) { + spdlog::warn( + "Since Memgraph was not supposed to recover on startup and " + "durability is enabled, your current durability files will likely " + "be overridden. To prevent important data loss, Memgraph has stored " + "those files into a .backup directory inside the storage directory."); + } + } + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { + snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { + if (auto maybe_error = this->CreateSnapshot({true}); maybe_error.HasError()) { + switch (maybe_error.GetError()) { + case CreateSnapshotError::DisabledForReplica: + spdlog::warn( + utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); + break; + case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: + spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.", + "https://memgr.ph/durability")); + break; + case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries: + spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support"); + break; + } + } + }); + } + if (config_.gc.type == Config::Gc::Type::PERIODIC) { + gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->CollectGarbage<false>(); }); + } + + if (timestamp_ == kTimestampInitialId) { + commit_log_.emplace(); + } else { + commit_log_.emplace(timestamp_); + } + + if (config_.durability.restore_replication_state_on_startup) { + spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash."); + utils::EnsureDirOrDie(config_.durability.storage_directory / durability::kReplicationDirectory); + storage_ = + std::make_unique<kvstore::KVStore>(config_.durability.storage_directory / durability::kReplicationDirectory); + + RestoreReplicationRole(); + + if (replication_role_ == replication::ReplicationRole::MAIN) { + RestoreReplicas(); + } + } else { + spdlog::warn( + "Replicastion configuration will NOT be stored. When the server restarts, replication state will be " + "forgotten."); + } + + if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && + replication_role_ == replication::ReplicationRole::MAIN) { + spdlog::warn( + "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider " + "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " + "without write-ahead logs this instance is not replicating any data."); + } +} + +InMemoryStorage::~InMemoryStorage() { + if (config_.gc.type == Config::Gc::Type::PERIODIC) { + gc_runner_.Stop(); + } + { + // Clear replication data + replication_server_.reset(); + replication_clients_.WithLock([&](auto &clients) { clients.clear(); }); + } + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_ = std::nullopt; + } + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { + snapshot_runner_.Stop(); + } + if (config_.durability.snapshot_on_exit) { + if (auto maybe_error = this->CreateSnapshot({false}); maybe_error.HasError()) { + switch (maybe_error.GetError()) { + case CreateSnapshotError::DisabledForReplica: + spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); + break; + case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: + spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.", + "https://memgr.ph/replication")); + break; + case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries: + spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support"); + break; + } + } + } +} + +InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryStorage *storage, IsolationLevel isolation_level, + StorageMode storage_mode) + : Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) {} +InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept + : Accessor(std::move(other)), config_(other.config_) {} + +InMemoryStorage::InMemoryAccessor::~InMemoryAccessor() { + if (is_transaction_active_) { + Abort(); + } + + FinalizeTransaction(); +} + +VertexAccessor InMemoryStorage::InMemoryAccessor::CreateVertex() { + OOMExceptionEnabler oom_exception; + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + auto gid = mem_storage->vertex_id_.fetch_add(1, std::memory_order_acq_rel); + auto acc = mem_storage->vertices_.access(); + + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta}); + MG_ASSERT(inserted, "The vertex must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); + + if (delta) { + delta->prev.Set(&*it); + } + return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; +} + +VertexAccessor InMemoryStorage::InMemoryAccessor::CreateVertex(storage::Gid gid) { + OOMExceptionEnabler oom_exception; + // NOTE: When we update the next `vertex_id_` here we perform a RMW + // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue + // because this function is only called from the replication delta applier + // that runs single-threadedly and while this instance is set-up to apply + // threads (it is the replica), it is guaranteed that no other writes are + // possible. + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->vertex_id_.store(std::max(mem_storage->vertex_id_.load(std::memory_order_acquire), gid.AsUint() + 1), + std::memory_order_release); + auto acc = mem_storage->vertices_.access(); + + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Vertex{gid, delta}); + MG_ASSERT(inserted, "The vertex must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); + if (delta) { + delta->prev.Set(&*it); + } + return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; +} + +std::optional<VertexAccessor> InMemoryStorage::InMemoryAccessor::FindVertex(Gid gid, View view) { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + auto acc = mem_storage->vertices_.access(); + auto it = acc.find(gid); + if (it == acc.end()) return std::nullopt; + return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, view); +} + +Result<std::optional<VertexAccessor>> InMemoryStorage::InMemoryAccessor::DeleteVertex(VertexAccessor *vertex) { + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) { + return std::optional<VertexAccessor>{}; + } + + if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) return Error::VERTEX_HAS_EDGES; + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + + // Need to inform the next CollectGarbage call that there are some + // non-transactional deletions that need to be collected + if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->gc_full_scan_vertices_delete_ = true; + } + + return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, + config_, true); +} + +Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> +InMemoryStorage::InMemoryAccessor::DetachDeleteVertex(VertexAccessor *vertex) { + using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>; + + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; + std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; + + { + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) return std::optional<ReturnType>{}; + + in_edges = vertex_ptr->in_edges; + out_edges = vertex_ptr->out_edges; + } + + std::vector<EdgeAccessor> deleted_edges; + for (const auto &item : in_edges) { + auto [edge_type, from_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + for (const auto &item : out_edges) { + auto [edge_type, to_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, + config_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + // We need to check again for serialization errors because we unlocked the + // vertex. Some other transaction could have modified the vertex in the + // meantime if we didn't have any edges to delete. + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + + // Need to inform the next CollectGarbage call that there are some + // non-transactional deletions that need to be collected + if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->gc_full_scan_vertices_delete_ = true; + } + + return std::make_optional<ReturnType>( + VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true}, + std::move(deleted_edges)); +} + +Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, + EdgeTypeId edge_type) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(from->transaction_ == to->transaction_, + "VertexAccessors must be from the same transaction when creating " + "an edge!"); + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessors must be from the same transaction in when " + "creating an edge!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + auto gid = storage::Gid::FromUint(mem_storage->edge_id_.fetch_add(1, std::memory_order_acq_rel)); + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = mem_storage->edges_.access(); + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + if (delta) { + delta->prev.Set(&*it); + } + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + // Increment edge count. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); +} + +Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, + EdgeTypeId edge_type, storage::Gid gid) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(from->transaction_ == to->transaction_, + "VertexAccessors must be from the same transaction when creating " + "an edge!"); + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessors must be from the same transaction in when " + "creating an edge!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + // NOTE: When we update the next `edge_id_` here we perform a RMW + // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue + // because this function is only called from the replication delta applier + // that runs single-threadedly and while this instance is set-up to apply + // threads (it is the replica), it is guaranteed that no other writes are + // possible. + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->edge_id_.store(std::max(mem_storage->edge_id_.load(std::memory_order_acquire), gid.AsUint() + 1), + std::memory_order_release); + + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = mem_storage->edges_.access(); + + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + if (delta) { + delta->prev.Set(&*it); + } + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + // Increment edge count. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, + &storage_->constraints_, config_); +} + +Result<std::optional<EdgeAccessor>> InMemoryStorage::InMemoryAccessor::DeleteEdge(EdgeAccessor *edge) { + MG_ASSERT(edge->transaction_ == &transaction_, + "EdgeAccessor must be from the same transaction as the storage " + "accessor when deleting an edge!"); + auto edge_ref = edge->edge_; + auto edge_type = edge->edge_type_; + + std::unique_lock<utils::SpinLock> guard; + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + guard = std::unique_lock<utils::SpinLock>(edge_ptr->lock); + + if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; + + if (edge_ptr->deleted) return std::optional<EdgeAccessor>{}; + } + + auto *from_vertex = edge->from_vertex_; + auto *to_vertex = edge->to_vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + } + + auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { + std::tuple<EdgeTypeId, Vertex *, EdgeRef> link(edge_type, vertex, edge_ref); + auto it = std::find(edges->begin(), edges->end(), link); + if (config_.properties_on_edges) { + MG_ASSERT(it != edges->end(), "Invalid database state!"); + } else if (it == edges->end()) { + return false; + } + std::swap(*it, *edges->rbegin()); + edges->pop_back(); + return true; + }; + + auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); + auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); + + if (config_.properties_on_edges) { + MG_ASSERT((op1 && op2), "Invalid database state!"); + } else { + MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); + if (!op1 && !op2) { + // The edge is already deleted. + return std::optional<EdgeAccessor>{}; + } + } + + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag()); + edge_ptr->deleted = true; + + // Need to inform the next CollectGarbage call that there are some + // non-transactional deletions that need to be collected + if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->gc_full_scan_edges_delete_ = true; + } + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + + // Decrement edge count. + storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); + + return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, + &storage_->indices_, &storage_->constraints_, config_, true); +} + +// NOLINTNEXTLINE(google-default-arguments) +utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit( + const std::optional<uint64_t> desired_commit_timestamp) { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); + + auto could_replicate_all_sync_replicas = true; + + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + + if (transaction_.deltas.empty()) { + // We don't have to update the commit timestamp here because no one reads + // it. + mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp); + } else { + // Validate that existence constraints are satisfied for all modified + // vertices. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + // No need to take any locks here because we modified this vertex and no + // one else can touch it until we commit. + auto validation_result = storage_->constraints_.existence_constraints_->Validate(*prev.vertex); + if (validation_result) { + Abort(); + return StorageDataManipulationError{*validation_result}; + } + } + + // Result of validating the vertex against unqiue constraints. It has to be + // declared outside of the critical section scope because its value is + // tested for Abort call which has to be done out of the scope. + std::optional<ConstraintViolation> unique_constraint_violation; + + // Save these so we can mark them used in the commit log. + uint64_t start_timestamp = transaction_.start_timestamp; + + { + std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); + auto *mem_unique_constraints = + static_cast<InMemoryUniqueConstraints *>(storage_->constraints_.unique_constraints_.get()); + commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp)); + + // Before committing and validating vertices against unique constraints, + // we have to update unique constraints with the vertices that are going + // to be validated/committed. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + mem_unique_constraints->UpdateBeforeCommit(prev.vertex, transaction_); + } + + // Validate that unique constraints are satisfied for all modified + // vertices. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + + // No need to take any locks here because we modified this vertex and no + // one else can touch it until we commit. + unique_constraint_violation = mem_unique_constraints->Validate(*prev.vertex, transaction_, *commit_timestamp_); + if (unique_constraint_violation) { + break; + } + } + + if (!unique_constraint_violation) { + // Write transaction to WAL while holding the engine lock to make sure + // that committed transactions are sorted by the commit timestamp in the + // WAL files. We supply the new commit timestamp to the function so that + // it knows what will be the final commit timestamp. The WAL must be + // written before actually committing the transaction (before setting + // the commit timestamp) so that no other transaction can see the + // modifications before they are written to disk. + // Replica can log only the write transaction received from Main + // so the Wal files are consistent + if (mem_storage->replication_role_ == replication::ReplicationRole::MAIN || + desired_commit_timestamp.has_value()) { + could_replicate_all_sync_replicas = + mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_); + } + + // Take committed_transactions lock while holding the engine lock to + // make sure that committed transactions are sorted by the commit + // timestamp in the list. + mem_storage->committed_transactions_.WithLock([&](auto & /*committed_transactions*/) { + // TODO: release lock, and update all deltas to have a local copy + // of the commit timestamp + MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); + transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); + // Replica can only update the last commit timestamp with + // the commits received from main. + if (mem_storage->replication_role_ == replication::ReplicationRole::MAIN || + desired_commit_timestamp.has_value()) { + // Update the last commit timestamp + mem_storage->last_commit_timestamp_.store(*commit_timestamp_); + } + // Release engine lock because we don't have to hold it anymore + // and emplace back could take a long time. + engine_guard.unlock(); + }); + + mem_storage->commit_log_->MarkFinished(start_timestamp); + } + } + + if (unique_constraint_violation) { + Abort(); + return StorageDataManipulationError{*unique_constraint_violation}; + } + } + is_transaction_active_ = false; + + if (!could_replicate_all_sync_replicas) { + return StorageDataManipulationError{ReplicationError{}}; + } + + return {}; +} + +void InMemoryStorage::InMemoryAccessor::Abort() { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + + // We collect vertices and edges we've created here and then splice them into + // `deleted_vertices_` and `deleted_edges_` lists, instead of adding them one + // by one and acquiring lock every time. + std::list<Gid> my_deleted_vertices; + std::list<Gid> my_deleted_edges; + + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + switch (prev.type) { + case PreviousPtr::Type::VERTEX: { + auto *vertex = prev.vertex; + std::lock_guard<utils::SpinLock> guard(vertex->lock); + Delta *current = vertex->delta; + while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == + transaction_.transaction_id.load(std::memory_order_acquire)) { + switch (current->action) { + case Delta::Action::REMOVE_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); + MG_ASSERT(it != vertex->labels.end(), "Invalid database state!"); + std::swap(*it, *vertex->labels.rbegin()); + vertex->labels.pop_back(); + break; + } + case Delta::Action::ADD_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); + MG_ASSERT(it == vertex->labels.end(), "Invalid database state!"); + vertex->labels.push_back(current->label); + break; + } + case Delta::Action::SET_PROPERTY: { + vertex->properties.SetProperty(current->property.key, current->property.value); + break; + } + case Delta::Action::ADD_IN_EDGE: { + std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); + vertex->in_edges.push_back(link); + break; + } + case Delta::Action::ADD_OUT_EDGE: { + std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); + MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); + vertex->out_edges.push_back(link); + // Increment edge count. We only increment the count here because + // the information in `ADD_IN_EDGE` and `Edge/RECREATE_OBJECT` is + // redundant. Also, `Edge/RECREATE_OBJECT` isn't available when + // edge properties are disabled. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + break; + } + case Delta::Action::REMOVE_IN_EDGE: { + std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + MG_ASSERT(it != vertex->in_edges.end(), "Invalid database state!"); + std::swap(*it, *vertex->in_edges.rbegin()); + vertex->in_edges.pop_back(); + break; + } + case Delta::Action::REMOVE_OUT_EDGE: { + std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); + MG_ASSERT(it != vertex->out_edges.end(), "Invalid database state!"); + std::swap(*it, *vertex->out_edges.rbegin()); + vertex->out_edges.pop_back(); + // Decrement edge count. We only decrement the count here because + // the information in `REMOVE_IN_EDGE` and `Edge/DELETE_OBJECT` is + // redundant. Also, `Edge/DELETE_OBJECT` isn't available when edge + // properties are disabled. + storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + vertex->deleted = true; + my_deleted_vertices.push_back(vertex->gid); + break; + } + case Delta::Action::RECREATE_OBJECT: { + vertex->deleted = false; + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + vertex->delta = current; + if (current != nullptr) { + current->prev.Set(vertex); + } + + break; + } + case PreviousPtr::Type::EDGE: { + auto *edge = prev.edge; + std::lock_guard<utils::SpinLock> guard(edge->lock); + Delta *current = edge->delta; + while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == + transaction_.transaction_id.load(std::memory_order_acquire)) { + switch (current->action) { + case Delta::Action::SET_PROPERTY: { + edge->properties.SetProperty(current->property.key, current->property.value); + break; + } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: { + edge->deleted = true; + my_deleted_edges.push_back(edge->gid); + break; + } + case Delta::Action::RECREATE_OBJECT: { + edge->deleted = false; + break; + } + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: { + LOG_FATAL("Invalid database state!"); + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + edge->delta = current; + if (current != nullptr) { + current->prev.Set(edge); + } + + break; + } + case PreviousPtr::Type::DELTA: + // pointer probably couldn't be set because allocation failed + case PreviousPtr::Type::NULLPTR: + break; + } + } + + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + { + std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); + uint64_t mark_timestamp = storage_->timestamp_; + // Take garbage_undo_buffers lock while holding the engine lock to make + // sure that entries are sorted by mark timestamp in the list. + mem_storage->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { + // Release engine lock because we don't have to hold it anymore and + // emplace back could take a long time. + engine_guard.unlock(); + garbage_undo_buffers.emplace_back(mark_timestamp, std::move(transaction_.deltas)); + }); + mem_storage->deleted_vertices_.WithLock( + [&](auto &deleted_vertices) { deleted_vertices.splice(deleted_vertices.begin(), my_deleted_vertices); }); + mem_storage->deleted_edges_.WithLock( + [&](auto &deleted_edges) { deleted_edges.splice(deleted_edges.begin(), my_deleted_edges); }); + } + + mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp); + is_transaction_active_ = false; +} + +void InMemoryStorage::InMemoryAccessor::FinalizeTransaction() { + if (commit_timestamp_) { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + mem_storage->commit_log_->MarkFinished(*commit_timestamp_); + mem_storage->committed_transactions_.WithLock( + [&](auto &committed_transactions) { committed_transactions.emplace_back(std::move(transaction_)); }); + commit_timestamp_.reset(); + } +} + +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::CreateIndex( + LabelId label, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + auto *mem_label_index = static_cast<InMemoryLabelIndex *>(indices_.label_index_.get()); + if (!mem_label_index->CreateIndex(label, vertices_.access(), std::nullopt)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + const auto success = + AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelIndices); + + if (success) { + return {}; + } + + return StorageIndexDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::CreateIndex( + LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + auto *mem_label_property_index = static_cast<InMemoryLabelPropertyIndex *>(indices_.label_property_index_.get()); + if (!mem_label_property_index->CreateIndex(label, property, vertices_.access(), std::nullopt)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, + {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); + + if (success) { + return {}; + } + + return StorageIndexDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::DropIndex( + LabelId label, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + if (!indices_.label_index_->DropIndex(label)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = + AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelIndices); + + if (success) { + return {}; + } + + return StorageIndexDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::DropIndex( + LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + if (!indices_.label_property_index_->DropIndex(label, property)) { + return StorageIndexDefinitionError{IndexDefinitionError{}}; + } + // For a description why using `timestamp_` is correct, see + // `CreateIndex(LabelId label)`. + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, + {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + // We don't care if there is a replication error because on main node the change will go through + memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); + + if (success) { + return {}; + } + + return StorageIndexDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageExistenceConstraintDefinitionError, void> InMemoryStorage::CreateExistenceConstraint( + LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + + if (constraints_.existence_constraints_->ConstraintExists(label, property)) { + return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}}; + } + + if (auto violation = ExistenceConstraints::ValidateVerticesOnConstraint(vertices_.access(), label, property); + violation.has_value()) { + return StorageExistenceConstraintDefinitionError{violation.value()}; + } + + constraints_.existence_constraints_->InsertConstraint(label, property); + + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, + {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + if (success) { + return {}; + } + + return StorageExistenceConstraintDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageExistenceConstraintDroppingError, void> InMemoryStorage::DropExistenceConstraint( + LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + if (!constraints_.existence_constraints_->DropConstraint(label, property)) { + return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}}; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, + {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + if (success) { + return {}; + } + + return StorageExistenceConstraintDroppingError{ReplicationError{}}; +} + +utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> +InMemoryStorage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get()); + auto ret = mem_unique_constraints->CreateConstraint(label, properties, vertices_.access()); + if (ret.HasError()) { + return StorageUniqueConstraintDefinitionError{ret.GetError()}; + } + if (ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { + return ret.GetValue(); + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, + properties, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + if (success) { + return UniqueConstraints::CreationStatus::SUCCESS; + } + + return StorageUniqueConstraintDefinitionError{ReplicationError{}}; +} + +utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> +InMemoryStorage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + const std::optional<uint64_t> desired_commit_timestamp) { + std::unique_lock<utils::RWLock> storage_guard(main_lock_); + auto ret = constraints_.unique_constraints_->DropConstraint(label, properties); + if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { + return ret; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, + properties, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + + if (success) { + return UniqueConstraints::DeletionStatus::SUCCESS; + } + + return StorageUniqueConstraintDroppingError{ReplicationError{}}; +} + +VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, View view) { + auto *mem_label_index = static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()); + return VerticesIterable(mem_label_index->Vertices(label, view, &transaction_)); +} + +VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property, View view) { + auto *mem_label_property_index = + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + return VerticesIterable( + mem_label_property_index->Vertices(label, property, std::nullopt, std::nullopt, view, &transaction_)); +} + +VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, PropertyId property, + const PropertyValue &value, View view) { + auto *mem_label_property_index = + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + return VerticesIterable(mem_label_property_index->Vertices(label, property, utils::MakeBoundInclusive(value), + utils::MakeBoundInclusive(value), view, &transaction_)); +} + +VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices( + LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) { + auto *mem_label_property_index = + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()); + return VerticesIterable( + mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, &transaction_)); +} + +Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) { + // We acquire the transaction engine lock here because we access (and + // modify) the transaction engine variables (`transaction_id` and + // `timestamp`) below. + uint64_t transaction_id = 0; + uint64_t start_timestamp = 0; + { + std::lock_guard<utils::SpinLock> guard(engine_lock_); + transaction_id = transaction_id_++; + // Replica should have only read queries and the write queries + // can come from main instance with any past timestamp. + // To preserve snapshot isolation we set the start timestamp + // of any query on replica to the last commited transaction + // which is timestamp_ as only commit of transaction with writes + // can change the value of it. + if (replication_role_ == replication::ReplicationRole::REPLICA) { + start_timestamp = timestamp_; + } else { + start_timestamp = timestamp_++; + } + } + return {transaction_id, start_timestamp, isolation_level, storage_mode}; +} + +template <bool force> +void InMemoryStorage::CollectGarbage(std::unique_lock<utils::RWLock> main_guard) { + // NOTE: You do not need to consider cleanup of deleted object that occurred in + // different storage modes within the same CollectGarbage call. This is because + // SetStorageMode will ensure CollectGarbage is called before any new transactions + // with the new storage mode can start. + + // SetStorageMode will pass its unique_lock of main_lock_. We will use that lock, + // as reacquiring the lock would cause deadlock. Otherwise, we need to get our own + // lock. + if (!main_guard.owns_lock()) { + if constexpr (force) { + // We take the unique lock on the main storage lock, so we can forcefully clean + // everything we can + if (!main_lock_.try_lock()) { + CollectGarbage<false>(); + return; + } + } else { + // Because the garbage collector iterates through the indices and constraints + // to clean them up, it must take the main lock for reading to make sure that + // the indices and constraints aren't concurrently being modified. + main_lock_.lock_shared(); + } + } else { + MG_ASSERT(main_guard.mutex() == std::addressof(main_lock_), "main_guard should be only for the main_lock_"); + } + + utils::OnScopeExit lock_releaser{[&] { + if (!main_guard.owns_lock()) { + if constexpr (force) { + main_lock_.unlock(); + } else { + main_lock_.unlock_shared(); + } + } else { + main_guard.unlock(); + } + }}; + + // Garbage collection must be performed in two phases. In the first phase, + // deltas that won't be applied by any transaction anymore are unlinked from + // the version chains. They cannot be deleted immediately, because there + // might be a transaction that still needs them to terminate the version + // chain traversal. They are instead marked for deletion and will be deleted + // in the second GC phase in this GC iteration or some of the following + // ones. + std::unique_lock<std::mutex> gc_guard(gc_lock_, std::try_to_lock); + if (!gc_guard.owns_lock()) { + return; + } + + uint64_t oldest_active_start_timestamp = commit_log_->OldestActive(); + // We don't move undo buffers of unlinked transactions to garbage_undo_buffers + // list immediately, because we would have to repeatedly take + // garbage_undo_buffers lock. + std::list<std::pair<uint64_t, std::list<Delta>>> unlinked_undo_buffers; + + // We will only free vertices deleted up until now in this GC cycle, and we + // will do it after cleaning-up the indices. That way we are sure that all + // vertices that appear in an index also exist in main storage. + std::list<Gid> current_deleted_edges; + std::list<Gid> current_deleted_vertices; + deleted_vertices_->swap(current_deleted_vertices); + deleted_edges_->swap(current_deleted_edges); + + auto const need_full_scan_vertices = gc_full_scan_vertices_delete_.exchange(false); + auto const need_full_scan_edges = gc_full_scan_edges_delete_.exchange(false); + + // Flag that will be used to determine whether the Index GC should be run. It + // should be run when there were any items that were cleaned up (there were + // updates between this run of the GC and the previous run of the GC). This + // eliminates high CPU usage when the GC doesn't have to clean up anything. + bool run_index_cleanup = !committed_transactions_->empty() || !garbage_undo_buffers_->empty() || + need_full_scan_vertices || need_full_scan_edges; + + while (true) { + // We don't want to hold the lock on committed transactions for too long, + // because that prevents other transactions from committing. + Transaction *transaction = nullptr; + { + auto committed_transactions_ptr = committed_transactions_.Lock(); + if (committed_transactions_ptr->empty()) { + break; + } + transaction = &committed_transactions_ptr->front(); + } + + auto commit_timestamp = transaction->commit_timestamp->load(std::memory_order_acquire); + if (commit_timestamp >= oldest_active_start_timestamp) { + break; + } + + // When unlinking a delta which is the first delta in its version chain, + // special care has to be taken to avoid the following race condition: + // + // [Vertex] --> [Delta A] + // + // GC thread: Delta A is the first in its chain, it must be unlinked from + // vertex and marked for deletion + // TX thread: Update vertex and add Delta B with Delta A as next + // + // [Vertex] --> [Delta B] <--> [Delta A] + // + // GC thread: Unlink delta from Vertex + // + // [Vertex] --> (nullptr) + // + // When processing a delta that is the first one in its chain, we + // obtain the corresponding vertex or edge lock, and then verify that this + // delta still is the first in its chain. + // When processing a delta that is in the middle of the chain we only + // process the final delta of the given transaction in that chain. We + // determine the owner of the chain (either a vertex or an edge), obtain the + // corresponding lock, and then verify that this delta is still in the same + // position as it was before taking the lock. + // + // Even though the delta chain is lock-free (both `next` and `prev`) the + // chain should not be modified without taking the lock from the object that + // owns the chain (either a vertex or an edge). Modifying the chain without + // taking the lock will cause subtle race conditions that will leave the + // chain in a broken state. + // The chain can be only read without taking any locks. + + for (Delta &delta : transaction->deltas) { + while (true) { + auto prev = delta.prev.Get(); + switch (prev.type) { + case PreviousPtr::Type::VERTEX: { + Vertex *vertex = prev.vertex; + std::lock_guard<utils::SpinLock> vertex_guard(vertex->lock); + if (vertex->delta != &delta) { + // Something changed, we're not the first delta in the chain + // anymore. + continue; + } + vertex->delta = nullptr; + if (vertex->deleted) { + current_deleted_vertices.push_back(vertex->gid); + } + break; + } + case PreviousPtr::Type::EDGE: { + Edge *edge = prev.edge; + std::lock_guard<utils::SpinLock> edge_guard(edge->lock); + if (edge->delta != &delta) { + // Something changed, we're not the first delta in the chain + // anymore. + continue; + } + edge->delta = nullptr; + if (edge->deleted) { + current_deleted_edges.push_back(edge->gid); + } + break; + } + case PreviousPtr::Type::DELTA: { + if (prev.delta->timestamp->load(std::memory_order_acquire) == commit_timestamp) { + // The delta that is newer than this one is also a delta from this + // transaction. We skip the current delta and will remove it as a + // part of the suffix later. + break; + } + std::unique_lock<utils::SpinLock> guard; + { + // We need to find the parent object in order to be able to use + // its lock. + auto parent = prev; + while (parent.type == PreviousPtr::Type::DELTA) { + parent = parent.delta->prev.Get(); + } + switch (parent.type) { + case PreviousPtr::Type::VERTEX: + guard = std::unique_lock<utils::SpinLock>(parent.vertex->lock); + break; + case PreviousPtr::Type::EDGE: + guard = std::unique_lock<utils::SpinLock>(parent.edge->lock); + break; + case PreviousPtr::Type::DELTA: + case PreviousPtr::Type::NULLPTR: + LOG_FATAL("Invalid database state!"); + } + } + if (delta.prev.Get() != prev) { + // Something changed, we could now be the first delta in the + // chain. + continue; + } + Delta *prev_delta = prev.delta; + prev_delta->next.store(nullptr, std::memory_order_release); + break; + } + case PreviousPtr::Type::NULLPTR: { + LOG_FATAL("Invalid pointer!"); + } + } + break; + } + } + + committed_transactions_.WithLock([&](auto &committed_transactions) { + unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); + committed_transactions.pop_front(); + }); + } + + // After unlinking deltas from vertices, we refresh the indices. That way + // we're sure that none of the vertices from `current_deleted_vertices` + // appears in an index, and we can safely remove the from the main storage + // after the last currently active transaction is finished. + if (run_index_cleanup) { + // This operation is very expensive as it traverses through all of the items + // in every index every time. + indices_.RemoveObsoleteEntries(oldest_active_start_timestamp); + auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get()); + mem_unique_constraints->RemoveObsoleteEntries(oldest_active_start_timestamp); + } + + { + std::unique_lock<utils::SpinLock> guard(engine_lock_); + uint64_t mark_timestamp = timestamp_; + // Take garbage_undo_buffers lock while holding the engine lock to make + // sure that entries are sorted by mark timestamp in the list. + garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { + // Release engine lock because we don't have to hold it anymore and + // this could take a long time. + guard.unlock(); + // TODO(mtomic): holding garbage_undo_buffers_ lock here prevents + // transactions from aborting until we're done marking, maybe we should + // add them one-by-one or something + for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { + timestamp = mark_timestamp; + } + garbage_undo_buffers.splice(garbage_undo_buffers.end(), unlinked_undo_buffers); + }); + for (auto vertex : current_deleted_vertices) { + garbage_vertices_.emplace_back(mark_timestamp, vertex); + } + } + + garbage_undo_buffers_.WithLock([&](auto &undo_buffers) { + // if force is set to true we can simply delete all the leftover undos because + // no transaction is active + if constexpr (force) { + undo_buffers.clear(); + } else { + while (!undo_buffers.empty() && undo_buffers.front().first <= oldest_active_start_timestamp) { + undo_buffers.pop_front(); + } + } + }); + + { + auto vertex_acc = vertices_.access(); + if constexpr (force) { + // if force is set to true, then we have unique_lock and no transactions are active + // so we can clean all of the deleted vertices + while (!garbage_vertices_.empty()) { + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + garbage_vertices_.pop_front(); + } + } else { + while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + garbage_vertices_.pop_front(); + } + } + } + { + auto edge_acc = edges_.access(); + for (auto edge : current_deleted_edges) { + MG_ASSERT(edge_acc.remove(edge), "Invalid database state!"); + } + } + + // EXPENSIVE full scan, is only run if an IN_MEMORY_ANALYTICAL transaction involved any deletions + // TODO: implement a fast internal iteration inside the skip_list (to avoid unnecessary find_node calls), + // accessor.remove_if([](auto const & item){ return item.delta == nullptr && item.deleted;}); + // alternatively, an auxiliary data structure within skip_list to track these, hence a full scan wouldn't be needed + // we will wait for evidence that this is needed before doing so. + if (need_full_scan_vertices) { + auto vertex_acc = vertices_.access(); + for (auto &vertex : vertex_acc) { + // a deleted vertex which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion + if (vertex.delta == nullptr && vertex.deleted) { + vertex_acc.remove(vertex); + } + } + } + + // EXPENSIVE full scan, is only run if an IN_MEMORY_ANALYTICAL transaction involved any deletions + if (need_full_scan_edges) { + auto edge_acc = edges_.access(); + for (auto &edge : edge_acc) { + // a deleted edge which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion + if (edge.delta == nullptr && edge.deleted) { + edge_acc.remove(edge); + } + } + } +} + +// tell the linker he can find the CollectGarbage definitions here +template void InMemoryStorage::CollectGarbage<true>(std::unique_lock<utils::RWLock>); +template void InMemoryStorage::CollectGarbage<false>(std::unique_lock<utils::RWLock>); + +StorageInfo InMemoryStorage::GetInfo() const { + auto vertex_count = vertices_.size(); + auto edge_count = edge_count_.load(std::memory_order_acquire); + double average_degree = 0.0; + if (vertex_count) { + // NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + average_degree = 2.0 * static_cast<double>(edge_count) / vertex_count; + } + return {vertex_count, edge_count, average_degree, utils::GetMemoryUsage(), + utils::GetDirDiskUsage(config_.durability.storage_directory)}; +} + +bool InMemoryStorage::InitializeWalFile() { + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) + return false; + if (!wal_file_) { + wal_file_.emplace(wal_directory_, uuid_, epoch_id_, config_.items, name_id_mapper_.get(), wal_seq_num_++, + &file_retainer_); + } + return true; +} + +void InMemoryStorage::FinalizeWalFile() { + ++wal_unsynced_transactions_; + if (wal_unsynced_transactions_ >= config_.durability.wal_file_flush_every_n_tx) { + wal_file_->Sync(); + wal_unsynced_transactions_ = 0; + } + if (wal_file_->GetSize() / 1024 >= config_.durability.wal_file_size_kibibytes) { + wal_file_->FinalizeWal(); + wal_file_ = std::nullopt; + wal_unsynced_transactions_ = 0; + } else { + // Try writing the internal buffer if possible, if not + // the data should be written as soon as it's possible + // (triggered by the new transaction commit, or some + // reading thread EnabledFlushing) + wal_file_->TryFlushing(); + } +} + +bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) { + if (!InitializeWalFile()) { + return true; + } + // Traverse deltas and append them to the WAL file. + // A single transaction will always be contained in a single WAL file. + auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); + + if (replication_role_.load() == replication::ReplicationRole::MAIN) { + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->StartTransactionReplication(wal_file_->SequenceNumber()); + } + }); + } + + // Helper lambda that traverses the delta chain on order to find the first + // delta that should be processed and then appends all discovered deltas. + auto find_and_apply_deltas = [&](const auto *delta, const auto &parent, auto filter) { + while (true) { + auto *older = delta->next.load(std::memory_order_acquire); + if (older == nullptr || older->timestamp->load(std::memory_order_acquire) != current_commit_timestamp) break; + delta = older; + } + while (true) { + if (filter(delta->action)) { + wal_file_->AppendDelta(*delta, parent, final_commit_timestamp); + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->IfStreamingTransaction( + [&](auto &stream) { stream.AppendDelta(*delta, parent, final_commit_timestamp); }); + } + }); + } + auto prev = delta->prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::DELTA) break; + delta = prev.delta; + } + }; + + // The deltas are ordered correctly in the `transaction.deltas` buffer, but we + // don't traverse them in that order. That is because for each delta we need + // information about the vertex or edge they belong to and that information + // isn't stored in the deltas themselves. In order to find out information + // about the corresponding vertex or edge it is necessary to traverse the + // delta chain for each delta until a vertex or edge is encountered. This + // operation is very expensive as the chain grows. + // Instead, we traverse the edges until we find a vertex or edge and traverse + // their delta chains. This approach has a drawback because we lose the + // correct order of the operations. Because of that, we need to traverse the + // deltas several times and we have to manually ensure that the stored deltas + // will be ordered correctly. + + // 1. Process all Vertex deltas and store all operations that create vertices + // and modify vertex data. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + return true; + + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + // 2. Process all Vertex deltas and store all operations that create edges. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::REMOVE_OUT_EDGE: + return true; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + return false; + } + }); + } + // 3. Process all Edge deltas and store all operations that modify edge data. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::EDGE) continue; + find_and_apply_deltas(&delta, *prev.edge, [](auto action) { + switch (action) { + case Delta::Action::SET_PROPERTY: + return true; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + // 4. Process all Vertex deltas and store all operations that delete edges. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::ADD_OUT_EDGE: + return true; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + // 5. Process all Vertex deltas and store all operations that delete vertices. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::RECREATE_OBJECT: + return true; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: + case Delta::Action::DELETE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + + // Add a delta that indicates that the transaction is fully written to the WAL + // file. + wal_file_->AppendTransactionEnd(final_commit_timestamp); + + FinalizeWalFile(); + + auto finalized_on_all_replicas = true; + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); }); + const auto finalized = client->FinalizeTransactionReplication(); + + if (client->Mode() == replication::ReplicationMode::SYNC) { + finalized_on_all_replicas = finalized && finalized_on_all_replicas; + } + } + }); + + return finalized_on_all_replicas; +} + +bool InMemoryStorage::AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label, + const std::set<PropertyId> &properties, + uint64_t final_commit_timestamp) { + if (!InitializeWalFile()) { + return true; + } + + auto finalized_on_all_replicas = true; + wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp); + { + if (replication_role_.load() == replication::ReplicationRole::MAIN) { + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->StartTransactionReplication(wal_file_->SequenceNumber()); + client->IfStreamingTransaction( + [&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); }); + + const auto finalized = client->FinalizeTransactionReplication(); + if (client->Mode() == replication::ReplicationMode::SYNC) { + finalized_on_all_replicas = finalized && finalized_on_all_replicas; + } + } + }); + } + } + FinalizeWalFile(); + return finalized_on_all_replicas; +} + +utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot( + std::optional<bool> is_periodic) { + if (replication_role_.load() != replication::ReplicationRole::MAIN) { + return CreateSnapshotError::DisabledForReplica; + } + + auto snapshot_creator = [this]() { + utils::Timer timer; + + auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_); + // Create snapshot. + durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, + config_.durability.snapshot_retention_count, &vertices_, &edges_, name_id_mapper_.get(), + &indices_, &constraints_, config_, uuid_, epoch_id_, epoch_history_, &file_retainer_); + // Finalize snapshot transaction. + commit_log_->MarkFinished(transaction.start_timestamp); + + memgraph::metrics::Measure(memgraph::metrics::SnapshotCreationLatency_us, + std::chrono::duration_cast<std::chrono::microseconds>(timer.Elapsed()).count()); + }; + + std::lock_guard snapshot_guard(snapshot_lock_); + + auto should_try_shared{true}; + auto max_num_tries{10}; + while (max_num_tries) { + if (should_try_shared) { + std::shared_lock<utils::RWLock> storage_guard(main_lock_); + if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL) { + snapshot_creator(); + return {}; + } + } else { + std::unique_lock main_guard{main_lock_}; + if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL) { + if (is_periodic && *is_periodic) { + return CreateSnapshotError::DisabledForAnalyticsPeriodicCommit; + } + snapshot_creator(); + return {}; + } + } + should_try_shared = !should_try_shared; + max_num_tries--; + } + + return CreateSnapshotError::ReachedMaxNumTries; +} + +void InMemoryStorage::FreeMemory(std::unique_lock<utils::RWLock> main_guard) { + CollectGarbage<true>(std::move(main_guard)); + + // SkipList is already threadsafe + vertices_.run_gc(); + edges_.run_gc(); + + static_cast<InMemoryLabelIndex *>(indices_.label_index_.get())->RunGC(); + static_cast<InMemoryLabelPropertyIndex *>(indices_.label_property_index_.get())->RunGC(); +} + +uint64_t InMemoryStorage::CommitTimestamp(const std::optional<uint64_t> desired_commit_timestamp) { + if (!desired_commit_timestamp) { + return timestamp_++; + } + timestamp_ = std::max(timestamp_, *desired_commit_timestamp + 1); + return *desired_commit_timestamp; +} + +bool InMemoryStorage::SetReplicaRole(io::network::Endpoint endpoint, + const replication::ReplicationServerConfig &config) { + // We don't want to restart the server if we're already a REPLICA + if (replication_role_ == replication::ReplicationRole::REPLICA) { + return false; + } + + auto port = endpoint.port; // assigning because we will move the endpoint + replication_server_ = std::make_unique<ReplicationServer>(this, std::move(endpoint), config); + + if (ShouldStoreAndRestoreReplicationState()) { + // Only thing that matters here is the role saved as REPLICA and the listening port + auto data = replication::ReplicationStatusToJSON( + replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName, + .ip_address = "", + .port = port, + .sync_mode = replication::ReplicationMode::SYNC, + .replica_check_frequency = std::chrono::seconds(0), + .ssl = std::nullopt, + .role = replication::ReplicationRole::REPLICA}); + + if (!storage_->Put(replication::kReservedReplicationRoleName, data.dump())) { + spdlog::error("Error when saving REPLICA replication role in settings."); + return false; + } + } + + replication_role_.store(replication::ReplicationRole::REPLICA); + return true; +} + +bool InMemoryStorage::SetMainReplicationRole() { + // We don't want to generate new epoch_id and do the + // cleanup if we're already a MAIN + if (replication_role_ == replication::ReplicationRole::MAIN) { + return false; + } + + // Main instance does not need replication server + // This should be always called first so we finalize everything + replication_server_.reset(nullptr); + + { + std::unique_lock engine_guard{engine_lock_}; + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_.reset(); + } + + // 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(); + } + + if (ShouldStoreAndRestoreReplicationState()) { + // Only thing that matters here is the role saved as MAIN + auto data = replication::ReplicationStatusToJSON( + replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName, + .ip_address = "", + .port = 0, + .sync_mode = replication::ReplicationMode::SYNC, + .replica_check_frequency = std::chrono::seconds(0), + .ssl = std::nullopt, + .role = replication::ReplicationRole::MAIN}); + + if (!storage_->Put(replication::kReservedReplicationRoleName, data.dump())) { + spdlog::error("Error when saving MAIN replication role in settings."); + return false; + } + } + + replication_role_.store(replication::ReplicationRole::MAIN); + return true; +} + +utils::BasicResult<InMemoryStorage::RegisterReplicaError> InMemoryStorage::RegisterReplica( + std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode, + const replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config) { + MG_ASSERT(replication_role_.load() == replication::ReplicationRole::MAIN, + "Only main instance can register a replica!"); + + const bool name_exists = replication_clients_.WithLock([&](auto &clients) { + return std::any_of(clients.begin(), clients.end(), [&name](const auto &client) { return client->Name() == name; }); + }); + + if (name_exists) { + return RegisterReplicaError::NAME_EXISTS; + } + + const auto end_point_exists = replication_clients_.WithLock([&endpoint](auto &clients) { + return std::any_of(clients.begin(), clients.end(), + [&endpoint](const auto &client) { return client->Endpoint() == endpoint; }); + }); + + if (end_point_exists) { + return RegisterReplicaError::END_POINT_EXISTS; + } + + if (ShouldStoreAndRestoreReplicationState()) { + auto data = replication::ReplicationStatusToJSON( + replication::ReplicationStatus{.name = name, + .ip_address = endpoint.address, + .port = endpoint.port, + .sync_mode = replication_mode, + .replica_check_frequency = config.replica_check_frequency, + .ssl = config.ssl, + .role = replication::ReplicationRole::REPLICA}); + if (!storage_->Put(name, data.dump())) { + spdlog::error("Error when saving replica {} in settings.", name); + return RegisterReplicaError::COULD_NOT_BE_PERSISTED; + } + } + + auto client = std::make_unique<ReplicationClient>(std::move(name), this, endpoint, replication_mode, config); + + if (client->State() == replication::ReplicaState::INVALID) { + if (replication::RegistrationMode::CAN_BE_INVALID != registration_mode) { + return RegisterReplicaError::CONNECTION_FAILED; + } + + spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.", client->Name()); + } + + return replication_clients_.WithLock([&](auto &clients) -> utils::BasicResult<InMemoryStorage::RegisterReplicaError> { + // Another thread could have added a client with same name while + // we were connecting to this client. + if (std::any_of(clients.begin(), clients.end(), + [&](const auto &other_client) { return client->Name() == other_client->Name(); })) { + return RegisterReplicaError::NAME_EXISTS; + } + + if (std::any_of(clients.begin(), clients.end(), + [&client](const auto &other_client) { return client->Endpoint() == other_client->Endpoint(); })) { + return RegisterReplicaError::END_POINT_EXISTS; + } + + clients.push_back(std::move(client)); + return {}; + }); +} + +bool InMemoryStorage::UnregisterReplica(const std::string &name) { + MG_ASSERT(replication_role_.load() == replication::ReplicationRole::MAIN, + "Only main instance can unregister a replica!"); + if (ShouldStoreAndRestoreReplicationState()) { + if (!storage_->Delete(name)) { + spdlog::error("Error when removing replica {} from settings.", name); + return false; + } + } + + return replication_clients_.WithLock([&](auto &clients) { + return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); + }); +} + +std::optional<replication::ReplicaState> InMemoryStorage::GetReplicaState(const std::string_view name) { + return replication_clients_.WithLock([&](auto &clients) -> std::optional<replication::ReplicaState> { + const auto client_it = + std::find_if(clients.cbegin(), clients.cend(), [name](auto &client) { return client->Name() == name; }); + if (client_it == clients.cend()) { + return std::nullopt; + } + return (*client_it)->State(); + }); +} + +replication::ReplicationRole InMemoryStorage::GetReplicationRole() const { return replication_role_; } + +std::vector<InMemoryStorage::ReplicaInfo> InMemoryStorage::ReplicasInfo() { + return replication_clients_.WithLock([](auto &clients) { + std::vector<InMemoryStorage::ReplicaInfo> replica_info; + replica_info.reserve(clients.size()); + std::transform( + clients.begin(), clients.end(), std::back_inserter(replica_info), [](const auto &client) -> ReplicaInfo { + return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo()}; + }); + return replica_info; + }); +} + +void InMemoryStorage::RestoreReplicationRole() { + if (!ShouldStoreAndRestoreReplicationState()) { + return; + } + + spdlog::info("Restoring replication role."); + + uint16_t port = replication::kDefaultReplicationPort; + for (const auto &[replica_name, replica_data] : *storage_) { + const auto maybe_replica_status = replication::JSONToReplicationStatus(nlohmann::json::parse(replica_data)); + if (!maybe_replica_status.has_value()) { + LOG_FATAL("Cannot parse previously saved configuration of replica {}.", replica_name); + } + + if (replica_name != replication::kReservedReplicationRoleName) { + continue; + } + + auto replica_status = *maybe_replica_status; + + if (!replica_status.role.has_value()) { + replication_role_.store(replication::ReplicationRole::MAIN); + } else { + replication_role_.store(*replica_status.role); + port = replica_status.port; + } + + break; + } + + if (replication_role_ == replication::ReplicationRole::REPLICA) { + io::network::Endpoint endpoint(replication::kDefaultReplicationServerIp, port); + replication_server_ = + std::make_unique<ReplicationServer>(this, std::move(endpoint), replication::ReplicationServerConfig{}); + } + + spdlog::info("Replication role restored to {}.", + replication_role_ == replication::ReplicationRole::MAIN ? "MAIN" : "REPLICA"); +} + +void InMemoryStorage::RestoreReplicas() { + if (!ShouldStoreAndRestoreReplicationState()) { + return; + } + spdlog::info("Restoring replicas."); + + for (const auto &[replica_name, replica_data] : *storage_) { + spdlog::info("Restoring replica {}.", replica_name); + + const auto maybe_replica_status = replication::JSONToReplicationStatus(nlohmann::json::parse(replica_data)); + if (!maybe_replica_status.has_value()) { + LOG_FATAL("Cannot parse previously saved configuration of replica {}.", replica_name); + } + + auto replica_status = *maybe_replica_status; + MG_ASSERT(replica_status.name == replica_name, "Expected replica name is '{}', but got '{}'", replica_status.name, + replica_name); + + if (replica_name == replication::kReservedReplicationRoleName) { + continue; + } + + auto ret = + RegisterReplica(std::move(replica_status.name), {std::move(replica_status.ip_address), replica_status.port}, + replica_status.sync_mode, replication::RegistrationMode::CAN_BE_INVALID, + { + .replica_check_frequency = replica_status.replica_check_frequency, + .ssl = replica_status.ssl, + }); + + if (ret.HasError()) { + MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError()); + LOG_FATAL("Failure when restoring replica {}: {}.", replica_name, RegisterReplicaErrorToString(ret.GetError())); + } + spdlog::info("Replica {} restored.", replica_name); + } +} + +bool InMemoryStorage::ShouldStoreAndRestoreReplicationState() const { return nullptr != storage_; } + +utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::IsPathLocked() { + auto locker_accessor = global_locker_.Access(); + return locker_accessor.IsPathLocked(config_.durability.storage_directory); +} + +utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::LockPath() { + auto locker_accessor = global_locker_.Access(); + return locker_accessor.AddPath(config_.durability.storage_directory); +} + +utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath() { + { + auto locker_accessor = global_locker_.Access(); + const auto ret = locker_accessor.RemovePath(config_.durability.storage_directory); + if (ret.HasError() || !ret.GetValue()) { + // Exit without cleaning the queue + return ret; + } + } + // We use locker accessor in seperate scope so we don't produce deadlock + // after we call clean queue. + file_retainer_.CleanQueue(); + return true; +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp new file mode 100644 index 000000000..b45fce0ef --- /dev/null +++ b/src/storage/v2/inmemory/storage.hpp @@ -0,0 +1,522 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/inmemory/label_index.hpp" +#include "storage/v2/inmemory/label_property_index.hpp" +#include "storage/v2/storage.hpp" + +/// REPLICATION /// +#include "rpc/server.hpp" +#include "storage/v2/replication/config.hpp" +#include "storage/v2/replication/enums.hpp" +#include "storage/v2/replication/replication_persistence_helper.hpp" +#include "storage/v2/replication/rpc.hpp" +#include "storage/v2/replication/serialization.hpp" + +namespace memgraph::storage { + +// The storage is based on this paper: +// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf +// The paper implements a fully serializable storage, in our implementation we +// only implement snapshot isolation for transactions. + +class InMemoryStorage final : public Storage { + public: + enum class RegisterReplicaError : uint8_t { + NAME_EXISTS, + END_POINT_EXISTS, + CONNECTION_FAILED, + COULD_NOT_BE_PERSISTED + }; + + struct TimestampInfo { + uint64_t current_timestamp_of_replica; + uint64_t current_number_of_timestamp_behind_master; + }; + + struct ReplicaInfo { + std::string name; + replication::ReplicationMode mode; + io::network::Endpoint endpoint; + replication::ReplicaState state; + TimestampInfo timestamp_info; + }; + + enum class CreateSnapshotError : uint8_t { + DisabledForReplica, + DisabledForAnalyticsPeriodicCommit, + ReachedMaxNumTries + }; + + /// @throw std::system_error + /// @throw std::bad_alloc + explicit InMemoryStorage(Config config = Config()); + + InMemoryStorage(const InMemoryStorage &) = delete; + InMemoryStorage(InMemoryStorage &&) = delete; + InMemoryStorage &operator=(const InMemoryStorage &) = delete; + InMemoryStorage &operator=(InMemoryStorage &&) = delete; + + ~InMemoryStorage() override; + + class InMemoryAccessor final : public Storage::Accessor { + private: + friend class InMemoryStorage; + + explicit InMemoryAccessor(InMemoryStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode); + + public: + InMemoryAccessor(const InMemoryAccessor &) = delete; + InMemoryAccessor &operator=(const InMemoryAccessor &) = delete; + InMemoryAccessor &operator=(InMemoryAccessor &&other) = delete; + + // NOTE: After the accessor is moved, all objects derived from it (accessors + // and iterators) are *invalid*. You have to get all derived objects again. + InMemoryAccessor(InMemoryAccessor &&other) noexcept; + + ~InMemoryAccessor() override; + + /// @throw std::bad_alloc + VertexAccessor CreateVertex() override; + + std::optional<VertexAccessor> FindVertex(Gid gid, View view) override; + + VerticesIterable Vertices(View view) override { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + return VerticesIterable(AllVerticesIterable(mem_storage->vertices_.access(), &transaction_, view, + &mem_storage->indices_, &mem_storage->constraints_, + mem_storage->config_.items)); + } + + VerticesIterable Vertices(LabelId label, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) override; + + VerticesIterable Vertices(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) override; + + /// Return approximate number of all vertices in the database. + /// Note that this is always an over-estimate and never an under-estimate. + uint64_t ApproximateVertexCount() const override { + auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + return mem_storage->vertices_.size(); + } + + /// Return approximate number of vertices with the given label. + /// Note that this is always an over-estimate and never an under-estimate. + uint64_t ApproximateVertexCount(LabelId label) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->ApproximateVertexCount(label); + } + + /// Return approximate number of vertices with the given label and property. + /// Note that this is always an over-estimate and never an under-estimate. + uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount(label, + property); + } + + /// Return approximate number of vertices with the given label and the given + /// value for the given property. Note that this is always an over-estimate + /// and never an under-estimate. + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount( + label, property, value); + } + + /// Return approximate number of vertices with the given label and value for + /// the given property in the range defined by provided upper and lower + /// bounds. + uint64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->ApproximateVertexCount( + label, property, lower, upper); + } + + template <typename TResult, typename TIndex, typename TIndexKey> + std::optional<TResult> GetIndexStatsForIndex(TIndex *index, TIndexKey &&key) const { + return index->GetIndexStats(key); + } + + std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const override { + return GetIndexStatsForIndex<storage::LabelIndexStats>( + static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), label); + } + + std::optional<storage::LabelPropertyIndexStats> GetIndexStats(const storage::LabelId &label, + const storage::PropertyId &property) const override { + return GetIndexStatsForIndex<storage::LabelPropertyIndexStats>( + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()), + std::make_pair(label, property)); + } + + template <typename TIndex, typename TIndexKey, typename TIndexStats> + void SetIndexStatsForIndex(TIndex *index, TIndexKey &&key, TIndexStats &stats) const { + index->SetIndexStats(key, stats); + } + + void SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) override { + SetIndexStatsForIndex(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), label, stats); + } + + void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property, + const LabelPropertyIndexStats &stats) override { + SetIndexStatsForIndex(static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()), + std::make_pair(label, property), stats); + } + + template <typename TResult, typename TIndex> + std::vector<TResult> ClearIndexStatsForIndex(TIndex *index) const { + return index->ClearIndexStats(); + } + + std::vector<LabelId> ClearLabelIndexStats() override { + return ClearIndexStatsForIndex<LabelId>(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get())); + } + + std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() override { + return ClearIndexStatsForIndex<std::pair<LabelId, PropertyId>>( + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get())); + } + + template <typename TResult, typename TIndex> + std::vector<TResult> DeleteIndexStatsForIndex(TIndex *index, const std::span<std::string> labels) { + std::vector<TResult> deleted_indexes; + + for (const auto &label : labels) { + std::vector<TResult> loc_results = index->DeleteIndexStats(NameToLabel(label)); + deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()), + std::make_move_iterator(loc_results.end())); + } + return deleted_indexes; + } + + std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats( + const std::span<std::string> labels) override { + return DeleteIndexStatsForIndex<std::pair<LabelId, PropertyId>>( + static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()), labels); + } + + std::vector<LabelId> DeleteLabelIndexStats(const std::span<std::string> labels) override { + return DeleteIndexStatsForIndex<LabelId>(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), + labels); + } + + /// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex) override; + + /// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex( + VertexAccessor *vertex) override; + + void PrefetchInEdges(const VertexAccessor &vertex_acc) override{}; + + void PrefetchOutEdges(const VertexAccessor &vertex_acc) override{}; + + /// @throw std::bad_alloc + Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) override; + + /// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge) override; + + bool LabelIndexExists(LabelId label) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_index_->IndexExists(label); + } + + bool LabelPropertyIndexExists(LabelId label, PropertyId property) const override { + return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->IndexExists(label, property); + } + + IndicesInfo ListAllIndices() const override { + const auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + return mem_storage->ListAllIndices(); + } + + ConstraintsInfo ListAllConstraints() const override { + const auto *mem_storage = static_cast<InMemoryStorage *>(storage_); + return mem_storage->ListAllConstraints(); + } + + /// Returns void if the transaction has been committed. + /// Returns `StorageDataManipulationError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this + /// case the transaction is automatically aborted. + /// @throw std::bad_alloc + // NOLINTNEXTLINE(google-default-arguments) + utils::BasicResult<StorageDataManipulationError, void> Commit( + std::optional<uint64_t> desired_commit_timestamp = {}) override; + + /// @throw std::bad_alloc + void Abort() override; + + void FinalizeTransaction() override; + + private: + /// @throw std::bad_alloc + VertexAccessor CreateVertex(storage::Gid gid); + + /// @throw std::bad_alloc + Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid); + + Config::Items config_; + }; + + std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override { + return std::unique_ptr<InMemoryAccessor>( + new InMemoryAccessor{this, override_isolation_level.value_or(isolation_level_), storage_mode_}); + } + + /// Create an index. + /// Returns void if the index has been created. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `IndexDefinitionError`: the index already exists. + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// @throw std::bad_alloc + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Create an index. + /// Returns void if the index has been created. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `IndexDefinitionError`: the index already exists. + /// @throw std::bad_alloc + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Drop an existing index. + /// Returns void if the index has been dropped. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `IndexDefinitionError`: the index does not exist. + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Drop an existing index. + /// Returns void if the index has been dropped. + /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `IndexDefinitionError`: the index does not exist. + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Returns void if the existence constraint has been created. + /// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint. + /// * `ConstraintDefinitionError`: the constraint already exists. + /// @throw std::bad_alloc + /// @throw std::length_error + utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Drop an existing existence constraint. + /// Returns void if the existence constraint has been dropped. + /// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `ConstraintDefinitionError`: the constraint did not exists. + utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Create an unique constraint. + /// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// * `ConstraintViolation`: there are already vertices violating the constraint. + /// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be: + /// * `SUCCESS` if the constraint was successfully created, + /// * `ALREADY_EXISTS` if the constraint already existed, + /// * `EMPTY_PROPERTIES` if the property set is empty, or + /// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties. + /// @throw std::bad_alloc + utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint( + LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override; + + /// Removes an existing unique constraint. + /// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be: + /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. + /// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be: + /// * `SUCCESS` if constraint was successfully removed, + /// * `NOT_FOUND` if the specified constraint was not found, + /// * `EMPTY_PROPERTIES` if the property set is empty, or + /// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties. + utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint( + LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override; + + bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config); + + bool SetMainReplicationRole(); + + /// @pre The instance should have a MAIN role + /// @pre Timeout can only be set for SYNC replication + utils::BasicResult<RegisterReplicaError, void> RegisterReplica(std::string name, io::network::Endpoint endpoint, + replication::ReplicationMode replication_mode, + replication::RegistrationMode registration_mode, + const replication::ReplicationClientConfig &config); + /// @pre The instance should have a MAIN role + bool UnregisterReplica(const std::string &name); + + std::optional<replication::ReplicaState> GetReplicaState(std::string_view name); + + replication::ReplicationRole GetReplicationRole() const; + + std::vector<ReplicaInfo> ReplicasInfo(); + + void FreeMemory(std::unique_lock<utils::RWLock> main_guard) override; + + utils::FileRetainer::FileLockerAccessor::ret_type IsPathLocked(); + utils::FileRetainer::FileLockerAccessor::ret_type LockPath(); + utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath(); + + utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic); + + Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override; + + private: + /// The force parameter determines the behaviour of the garbage collector. + /// If it's set to true, it will behave as a global operation, i.e. it can't + /// be part of a transaction, and no other transaction can be active at the same time. + /// This allows it to delete immediately vertices without worrying that some other + /// transaction is possibly using it. If there are active transactions when this method + /// is called with force set to true, it will fallback to the same method with the force + /// set to false. + /// If it's set to false, it will execute in parallel with other transactions, ensuring + /// that no object in use can be deleted. + /// @throw std::system_error + /// @throw std::bad_alloc + template <bool force> + void CollectGarbage(std::unique_lock<utils::RWLock> main_guard = {}); + + bool InitializeWalFile(); + void FinalizeWalFile(); + + StorageInfo GetInfo() const override; + + /// Return true in all cases excepted if any sync replicas have not sent confirmation. + [[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp); + /// Return true in all cases excepted if any sync replicas have not sent confirmation. + [[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label, + const std::set<PropertyId> &properties, uint64_t final_commit_timestamp); + + uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {}); + + void RestoreReplicas(); + + void RestoreReplicationRole(); + + bool ShouldStoreAndRestoreReplicationState() const; + + // Main object storage + utils::SkipList<storage::Vertex> vertices_; + utils::SkipList<storage::Edge> edges_; + + // Durability + std::filesystem::path snapshot_directory_; + std::filesystem::path lock_file_path_; + utils::OutputFile lock_file_handle_; + std::unique_ptr<kvstore::KVStore> storage_; + std::filesystem::path wal_directory_; + + utils::Scheduler snapshot_runner_; + utils::SpinLock snapshot_lock_; + + // UUID used to distinguish snapshots and to link snapshots to WALs + std::string uuid_; + // 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<std::pair<std::string, uint64_t>> epoch_history_; + + std::optional<durability::WalFile> wal_file_; + uint64_t wal_unsynced_transactions_{0}; + + utils::FileRetainer file_retainer_; + + // Global locker that is used for clients file locking + utils::FileRetainer::FileLocker global_locker_; + + // TODO: This isn't really a commit log, it doesn't even care if a + // transaction commited or aborted. We could probably combine this with + // `timestamp_` in a sensible unit, something like TransactionClock or + // whatever. + std::optional<CommitLog> commit_log_; + utils::Synchronized<std::list<Transaction>, utils::SpinLock> committed_transactions_; + utils::Scheduler gc_runner_; + std::mutex gc_lock_; + + // Undo buffers that were unlinked and now are waiting to be freed. + utils::Synchronized<std::list<std::pair<uint64_t, std::list<Delta>>>, utils::SpinLock> garbage_undo_buffers_; + + // Vertices that are logically deleted but still have to be removed from + // indices before removing them from the main storage. + utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_; + + // Vertices that are logically deleted and removed from indices and now wait + // to be removed from the main storage. + std::list<std::pair<uint64_t, Gid>> garbage_vertices_; + + // Edges that are logically deleted and wait to be removed from the main + // storage. + utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_; + + // Flags to inform CollectGarbage that it needs to do the more expensive full scans + std::atomic<bool> gc_full_scan_vertices_delete_ = false; + std::atomic<bool> gc_full_scan_edges_delete_ = false; + + std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId}; + + class ReplicationServer; + std::unique_ptr<ReplicationServer> 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 initialize client in main thread which means + // that we can immediately notify the user if the initialization + // failed. + using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>; + ReplicationClientList replication_clients_; + + std::atomic<replication::ReplicationRole> replication_role_{replication::ReplicationRole::MAIN}; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/constraints.cpp b/src/storage/v2/inmemory/unique_constraints.cpp similarity index 80% rename from src/storage/v2/constraints.cpp rename to src/storage/v2/inmemory/unique_constraints.cpp index 12243f173..83507bc77 100644 --- a/src/storage/v2/constraints.cpp +++ b/src/storage/v2/inmemory/unique_constraints.cpp @@ -9,24 +9,17 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -#include "storage/v2/constraints.hpp" - -#include <algorithm> -#include <atomic> -#include <cstring> -#include <map> - -#include "storage/v2/mvcc.hpp" -#include "utils/logging.hpp" +#include "storage/v2/inmemory/unique_constraints.hpp" namespace memgraph::storage { + namespace { /// Helper function that determines position of the given `property` in the /// sorted `property_array` using binary search. In the case that `property` /// cannot be found, `std::nullopt` is returned. std::optional<size_t> FindPropertyPosition(const PropertyIdArray &property_array, PropertyId property) { - auto it = std::lower_bound(property_array.values, property_array.values + property_array.size, property); + const auto *it = std::lower_bound(property_array.values, property_array.values + property_array.size, property); if (it == property_array.values + property_array.size || *it != property) { return std::nullopt; } @@ -84,6 +77,7 @@ bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, c } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { MG_ASSERT(!deleted, "Invalid database state!"); deleted = true; @@ -198,6 +192,7 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std:: deleted = false; break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { MG_ASSERT(!deleted, "Invalid database state!"); deleted = true; @@ -225,30 +220,9 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std:: return false; } -/// Helper function that, given the set of `properties`, extracts corresponding -/// property values from the `vertex`. -/// @throw std::bad_alloc -std::optional<std::vector<PropertyValue>> ExtractPropertyValues(const Vertex &vertex, - const std::set<PropertyId> &properties) { - std::vector<PropertyValue> value_array; - value_array.reserve(properties.size()); - for (const auto &prop : properties) { - auto value = vertex.properties.GetProperty(prop); - if (value.IsNull()) { - return std::nullopt; - } - value_array.emplace_back(std::move(value)); - } - return std::move(value_array); -} - } // namespace -bool operator==(const ConstraintViolation &lhs, const ConstraintViolation &rhs) { - return lhs.type == rhs.type && lhs.label == rhs.label && lhs.properties == rhs.properties; -} - -bool UniqueConstraints::Entry::operator<(const Entry &rhs) { +bool InMemoryUniqueConstraints::Entry::operator<(const Entry &rhs) const { if (values < rhs.values) { return true; } @@ -258,20 +232,20 @@ bool UniqueConstraints::Entry::operator<(const Entry &rhs) { return std::make_tuple(vertex, timestamp) < std::make_tuple(rhs.vertex, rhs.timestamp); } -bool UniqueConstraints::Entry::operator==(const Entry &rhs) { +bool InMemoryUniqueConstraints::Entry::operator==(const Entry &rhs) const { return values == rhs.values && vertex == rhs.vertex && timestamp == rhs.timestamp; } -bool UniqueConstraints::Entry::operator<(const std::vector<PropertyValue> &rhs) { return values < rhs; } +bool InMemoryUniqueConstraints::Entry::operator<(const std::vector<PropertyValue> &rhs) const { return values < rhs; } -bool UniqueConstraints::Entry::operator==(const std::vector<PropertyValue> &rhs) { return values == rhs; } +bool InMemoryUniqueConstraints::Entry::operator==(const std::vector<PropertyValue> &rhs) const { return values == rhs; } -void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx) { +void InMemoryUniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx) { for (auto &[label_props, storage] : constraints_) { if (!utils::Contains(vertex->labels, label_props.first)) { continue; } - auto values = ExtractPropertyValues(*vertex, label_props.second); + auto values = vertex->properties.ExtractPropertyValues(label_props.second); if (values) { auto acc = storage.access(); acc.insert(Entry{std::move(*values), vertex, tx.start_timestamp}); @@ -279,8 +253,9 @@ void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transacti } } -utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> UniqueConstraints::CreateConstraint( - LabelId label, const std::set<PropertyId> &properties, utils::SkipList<Vertex>::Accessor vertices) { +utils::BasicResult<ConstraintViolation, InMemoryUniqueConstraints::CreationStatus> +InMemoryUniqueConstraints::CreateConstraint(LabelId label, const std::set<PropertyId> &properties, + utils::SkipList<Vertex>::Accessor vertices) { if (properties.empty()) { return CreationStatus::EMPTY_PROPERTIES; } @@ -305,7 +280,7 @@ utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> Uniqu if (vertex.deleted || !utils::Contains(vertex.labels, label)) { continue; } - auto values = ExtractPropertyValues(vertex, properties); + auto values = vertex.properties.ExtractPropertyValues(properties); if (!values) { continue; } @@ -331,13 +306,11 @@ utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> Uniqu return CreationStatus::SUCCESS; } -UniqueConstraints::DeletionStatus UniqueConstraints::DropConstraint(LabelId label, - const std::set<PropertyId> &properties) { - if (properties.empty()) { - return UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES; - } - if (properties.size() > kUniqueConstraintsMaxProperties) { - return UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED; +InMemoryUniqueConstraints::DeletionStatus InMemoryUniqueConstraints::DropConstraint( + LabelId label, const std::set<PropertyId> &properties) { + if (auto drop_properties_check_result = UniqueConstraints::CheckPropertiesBeforeDeletion(properties); + drop_properties_check_result != UniqueConstraints::DeletionStatus::SUCCESS) { + return drop_properties_check_result; } if (constraints_.erase({label, properties}) > 0) { return UniqueConstraints::DeletionStatus::SUCCESS; @@ -345,8 +318,12 @@ UniqueConstraints::DeletionStatus UniqueConstraints::DropConstraint(LabelId labe return UniqueConstraints::DeletionStatus::NOT_FOUND; } -std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &vertex, const Transaction &tx, - uint64_t commit_timestamp) const { +bool InMemoryUniqueConstraints::ConstraintExists(LabelId label, const std::set<PropertyId> &properties) const { + return constraints_.find({label, properties}) != constraints_.end(); +} + +std::optional<ConstraintViolation> InMemoryUniqueConstraints::Validate(const Vertex &vertex, const Transaction &tx, + uint64_t commit_timestamp) const { if (vertex.deleted) { return std::nullopt; } @@ -357,7 +334,7 @@ std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &ver continue; } - auto value_array = ExtractPropertyValues(vertex, properties); + auto value_array = vertex.properties.ExtractPropertyValues(properties); if (!value_array) { continue; } @@ -381,7 +358,7 @@ std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &ver return std::nullopt; } -std::vector<std::pair<LabelId, std::set<PropertyId>>> UniqueConstraints::ListConstraints() const { +std::vector<std::pair<LabelId, std::set<PropertyId>>> InMemoryUniqueConstraints::ListConstraints() const { std::vector<std::pair<LabelId, std::set<PropertyId>>> ret; ret.reserve(constraints_.size()); for (const auto &[label_props, _] : constraints_) { @@ -390,7 +367,7 @@ std::vector<std::pair<LabelId, std::set<PropertyId>>> UniqueConstraints::ListCon return ret; } -void UniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { +void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { for (auto &[label_props, storage] : constraints_) { auto acc = storage.access(); for (auto it = acc.begin(); it != acc.end();) { @@ -412,4 +389,6 @@ void UniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_times } } +void InMemoryUniqueConstraints::Clear() { constraints_.clear(); } + } // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/unique_constraints.hpp b/src/storage/v2/inmemory/unique_constraints.hpp new file mode 100644 index 000000000..401f1e036 --- /dev/null +++ b/src/storage/v2/inmemory/unique_constraints.hpp @@ -0,0 +1,101 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/constraints/unique_constraints.hpp" + +namespace memgraph::storage { + +/// Utility class to store data in a fixed size array. The array is used +/// instead of `std::vector` to avoid `std::bad_alloc` exception where not +/// necessary. +template <class T> +struct FixedCapacityArray { + size_t size; + T values[kUniqueConstraintsMaxProperties]; + + explicit FixedCapacityArray(size_t array_size) : size(array_size) { + MG_ASSERT(size <= kUniqueConstraintsMaxProperties, "Invalid array size!"); + } +}; + +using PropertyIdArray = FixedCapacityArray<PropertyId>; + +class InMemoryUniqueConstraints : public UniqueConstraints { + private: + struct Entry { + std::vector<PropertyValue> values; + const Vertex *vertex; + uint64_t timestamp; + + bool operator<(const Entry &rhs) const; + bool operator==(const Entry &rhs) const; + + bool operator<(const std::vector<PropertyValue> &rhs) const; + bool operator==(const std::vector<PropertyValue> &rhs) const; + }; + + public: + /// Indexes the given vertex for relevant labels and properties. + /// This method should be called before committing and validating vertices + /// against unique constraints. + /// @throw std::bad_alloc + void UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx); + + /// Creates unique constraint on the given `label` and a list of `properties`. + /// Returns constraint violation if there are multiple vertices with the same + /// label and property values. Returns `CreationStatus::ALREADY_EXISTS` if + /// constraint already existed, `CreationStatus::EMPTY_PROPERTIES` if the + /// given list of properties is empty, + /// `CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the list of properties + /// exceeds the maximum allowed number of properties, and + /// `CreationStatus::SUCCESS` on success. + /// @throw std::bad_alloc + utils::BasicResult<ConstraintViolation, CreationStatus> CreateConstraint(LabelId label, + const std::set<PropertyId> &properties, + utils::SkipList<Vertex>::Accessor vertices); + + /// Deletes the specified constraint. Returns `DeletionStatus::NOT_FOUND` if + /// there is not such constraint in the storage, + /// `DeletionStatus::EMPTY_PROPERTIES` if the given set of `properties` is + /// empty, `DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED` if the given set + /// of `properties` exceeds the maximum allowed number of properties, and + /// `DeletionStatus::SUCCESS` on success. + DeletionStatus DropConstraint(LabelId label, const std::set<PropertyId> &properties) override; + + bool ConstraintExists(LabelId label, const std::set<PropertyId> &properties) const override; + + void UpdateOnRemoveLabel(LabelId removed_label, const Vertex &vertex_before_update, + const uint64_t transaction_start_timestamp) override {} + + void UpdateOnAddLabel(LabelId added_label, const Vertex &vertex_before_update, + uint64_t transaction_start_timestamp) override{}; + + /// Validates the given vertex against unique constraints before committing. + /// This method should be called while commit lock is active with + /// `commit_timestamp` being a potential commit timestamp of the transaction. + /// @throw std::bad_alloc + std::optional<ConstraintViolation> Validate(const Vertex &vertex, const Transaction &tx, + uint64_t commit_timestamp) const; + + std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override; + + /// GC method that removes outdated entries from constraints' storages. + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + + void Clear() override; + + private: + std::map<std::pair<LabelId, std::set<PropertyId>>, utils::SkipList<Entry>> constraints_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/mvcc.hpp b/src/storage/v2/mvcc.hpp index 47f4aaca5..ed2c0560f 100644 --- a/src/storage/v2/mvcc.hpp +++ b/src/storage/v2/mvcc.hpp @@ -12,6 +12,7 @@ #pragma once #include <atomic> +#include <optional> #include "storage/v2/property_value.hpp" #include "storage/v2/transaction.hpp" @@ -60,7 +61,9 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie // We shouldn't undo our older changes because the user requested a OLD view // of the database. - if (view == View::OLD && ts == commit_timestamp && cid < transaction->command_id) { + if (view == View::OLD && ts == commit_timestamp && + (cid < transaction->command_id || + (cid == transaction->command_id && delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT))) { break; } @@ -80,7 +83,6 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie template <typename TObj> inline bool PrepareForWrite(Transaction *transaction, TObj *object) { if (object->delta == nullptr) return true; - auto ts = object->delta->timestamp->load(std::memory_order_acquire); if (ts == transaction->transaction_id.load(std::memory_order_acquire) || ts < transaction->start_timestamp) { return true; @@ -104,6 +106,20 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) { transaction->command_id); } +/// TODO: what if in-memory analytical +inline Delta *CreateDeleteDeserializedObjectDelta(Transaction *transaction, std::optional<std::string> old_disk_key) { + transaction->EnsureCommitTimestampExists(); + return &transaction->deltas.emplace_back(Delta::DeleteDeserializedObjectTag(), transaction->commit_timestamp.get(), + old_disk_key); +} + +/// TODO: what if in-memory analytical +inline Delta *CreateDeleteDeserializedIndexObjectDelta(Transaction *transaction, std::list<Delta> &deltas, + std::optional<std::string> old_disk_key) { + transaction->EnsureCommitTimestampExists(); + return &deltas.emplace_back(Delta::DeleteDeserializedObjectTag(), transaction->commit_timestamp.get(), old_disk_key); +} + /// This function creates a delta in the transaction for the object and links /// the delta into the object's delta list. /// @throw std::bad_alloc diff --git a/src/storage/v2/name_id_mapper.hpp b/src/storage/v2/name_id_mapper.hpp index 4ec79cb78..bb91e3647 100644 --- a/src/storage/v2/name_id_mapper.hpp +++ b/src/storage/v2/name_id_mapper.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -20,14 +20,14 @@ namespace memgraph::storage { -class NameIdMapper final { +class NameIdMapper { private: struct MapNameToId { std::string name; uint64_t id; - bool operator<(const MapNameToId &other) { return name < other.name; } - bool operator==(const MapNameToId &other) { return name == other.name; } + bool operator<(const MapNameToId &other) const { return name < other.name; } + bool operator==(const MapNameToId &other) const { return name == other.name; } bool operator<(const std::string_view other) const { return name < other; } bool operator==(const std::string_view other) const { return name == other; } @@ -37,16 +37,25 @@ class NameIdMapper final { uint64_t id; std::string name; - bool operator<(const MapIdToName &other) { return id < other.id; } - bool operator==(const MapIdToName &other) { return id == other.id; } + bool operator<(const MapIdToName &other) const { return id < other.id; } + bool operator==(const MapIdToName &other) const { return id == other.id; } - bool operator<(uint64_t other) { return id < other; } - bool operator==(uint64_t other) { return id == other; } + bool operator<(uint64_t other) const { return id < other; } + bool operator==(uint64_t other) const { return id == other; } }; public: + explicit NameIdMapper() = default; + + NameIdMapper(const NameIdMapper &) = delete; + NameIdMapper &operator=(const NameIdMapper &) = delete; + NameIdMapper(NameIdMapper &&) = delete; + NameIdMapper &operator=(NameIdMapper &&) = delete; + + virtual ~NameIdMapper() = default; + /// @throw std::bad_alloc if unable to insert a new mapping - uint64_t NameToId(const std::string_view name) { + virtual uint64_t NameToId(const std::string_view name) { auto name_to_id_acc = name_to_id_.access(); auto found = name_to_id_acc.find(name); uint64_t id; @@ -83,14 +92,22 @@ class NameIdMapper final { // Currently, we never delete anything from the `utils::SkipList` so the // references will always be valid. If you change this class to remove unused // names, be sure to change the signature of this function. - const std::string &IdToName(uint64_t id) const { + virtual const std::string &IdToName(uint64_t id) { + auto maybe_name = MaybeIdToName(id); + MG_ASSERT(maybe_name.has_value(), "Trying to get a name for an invalid ID!"); + return maybe_name.value(); + } + + protected: + std::optional<std::reference_wrapper<const std::string>> MaybeIdToName(uint64_t id) const { auto id_to_name_acc = id_to_name_.access(); auto result = id_to_name_acc.find(id); - MG_ASSERT(result != id_to_name_acc.end(), "Trying to get a name for an invalid ID!"); + if (result == id_to_name_acc.end()) { + return std::nullopt; + } return result->name; } - private: std::atomic<uint64_t> counter_{0}; utils::SkipList<MapNameToId> name_to_id_; utils::SkipList<MapIdToName> id_to_name_; diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp index d1bedc4e3..c7d0eea3a 100644 --- a/src/storage/v2/property_store.cpp +++ b/src/storage/v2/property_store.cpp @@ -11,9 +11,12 @@ #include "storage/v2/property_store.hpp" +#include <cstdint> #include <cstring> +#include <iterator> #include <limits> #include <optional> +#include <sstream> #include <tuple> #include <type_traits> #include <utility> @@ -984,6 +987,39 @@ bool PropertyStore::HasProperty(PropertyId property) const { return FindSpecificProperty(&reader, property, nullptr) == DecodeExpectedPropertyStatus::EQUAL; } +/// TODO: andi write a unit test for it +bool PropertyStore::HasAllProperties(const std::set<PropertyId> &properties) const { + return std::all_of(properties.begin(), properties.end(), [this](const auto &prop) { return HasProperty(prop); }); +} + +/// TODO: andi write a unit test for it +bool PropertyStore::HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const { + /// TODO: andi extract this into a private method + auto property_map = Properties(); + std::vector<PropertyValue> all_property_values; + transform(property_map.begin(), property_map.end(), back_inserter(all_property_values), + [](const auto &kv_entry) { return kv_entry.second; }); + + return std::all_of( + property_values.begin(), property_values.end(), [&all_property_values](const PropertyValue &value) { + return std::find(all_property_values.begin(), all_property_values.end(), value) != all_property_values.end(); + }); +} + +std::optional<std::vector<PropertyValue>> PropertyStore::ExtractPropertyValues( + const std::set<PropertyId> &properties) const { + std::vector<PropertyValue> value_array; + value_array.reserve(properties.size()); + for (const auto &prop : properties) { + auto value = GetProperty(prop); + if (value.IsNull()) { + return std::nullopt; + } + value_array.emplace_back(std::move(value)); + } + return value_array; +} + bool PropertyStore::IsPropertyEqual(PropertyId property, const PropertyValue &value) const { uint64_t size; const uint8_t *data; @@ -1234,4 +1270,41 @@ bool PropertyStore::ClearProperties() { return true; } +std::string PropertyStore::StringBuffer() const { + uint64_t size = 0; + const uint8_t *data = nullptr; + std::tie(size, data) = GetSizeData(buffer_); + if (size % 8 != 0) { // We are storing the data in the local buffer. + size = sizeof(buffer_) - 1; + data = &buffer_[1]; + } + std::string arr(size, ' '); + for (uint i = 0; i < size; ++i) { + arr[i] = static_cast<char>(data[i]); + } + return arr; +} + +void PropertyStore::SetBuffer(const std::string_view buffer) { + if (buffer.empty()) { + return; + } + + uint64_t size = 0; + uint8_t *data = nullptr; + if (buffer.size() == sizeof(buffer_) - 1) { // use local buffer + buffer_[0] = kUseLocalBuffer; + size = buffer.size() - 1; + data = &buffer_[1]; + } else { + size = buffer.size(); + data = new uint8_t[size]; + SetSizeData(buffer_, size, data); + } + + for (uint i = 0; i < size; ++i) { + data[i] = static_cast<uint8_t>(buffer[i]); + } +} + } // namespace memgraph::storage diff --git a/src/storage/v2/property_store.hpp b/src/storage/v2/property_store.hpp index 68d44147b..2cf785a7b 100644 --- a/src/storage/v2/property_store.hpp +++ b/src/storage/v2/property_store.hpp @@ -12,6 +12,7 @@ #pragma once #include <map> +#include <set> #include "storage/v2/id_types.hpp" #include "storage/v2/property_value.hpp" @@ -23,6 +24,12 @@ class PropertyStore { "PropertyStore supports only architectures using little-endian."); public: + static PropertyStore CreateFromBuffer(std::string_view buffer) { + PropertyStore store; + store.SetBuffer(buffer); + return store; + } + PropertyStore(); PropertyStore(const PropertyStore &) = delete; @@ -42,6 +49,19 @@ class PropertyStore { /// complexity of this function is O(n). bool HasProperty(PropertyId property) const; + /// Checks whether all properties in the set `properties` exist in the store. The time + /// complexity of this function is O(n^2). + bool HasAllProperties(const std::set<PropertyId> &properties) const; + + /// Checks whether all property values in the vector `property_values` exist in the store. The time + /// complexity of this function is O(n^2). + /// TODO: andi Not so sure it is quadratic complexity + bool HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const; + + /// Extracts property values for all property ids in the set `properties`. The time + /// complexity of this function is O(n^2). + std::optional<std::vector<PropertyValue>> ExtractPropertyValues(const std::set<PropertyId> &properties) const; + /// Checks whether the property `property` is equal to the specified value /// `value`. This function doesn't perform any memory allocations while /// performing the equality check. The time complexity of this function is @@ -77,6 +97,12 @@ class PropertyStore { /// @throw std::bad_alloc bool ClearProperties(); + /// Return property buffer as a string + std::string StringBuffer() const; + + /// Sets buffer + void SetBuffer(std::string_view buffer); + private: template <typename TContainer> bool DoInitProperties(const TContainer &properties); diff --git a/src/storage/v2/property_value.hpp b/src/storage/v2/property_value.hpp index 43481ac21..91ccc3b8b 100644 --- a/src/storage/v2/property_value.hpp +++ b/src/storage/v2/property_value.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -300,6 +300,7 @@ inline bool operator==(const PropertyValue &first, const PropertyValue &second) } } +/// NOLINTNEXTLINE(bugprone-exception-escape) inline bool operator<(const PropertyValue &first, const PropertyValue &second) noexcept { if (!PropertyValue::AreComparableTypes(first.type(), second.type())) return first.type() < second.type(); switch (first.type()) { @@ -330,6 +331,9 @@ inline bool operator<(const PropertyValue &first, const PropertyValue &second) n } } +/// NOLINTNEXTLINE(bugprone-exception-escape) +inline bool operator>(const PropertyValue &first, const PropertyValue &second) noexcept { return second < first; } + inline PropertyValue::PropertyValue(const PropertyValue &other) : type_(other.type_) { switch (other.type_) { case Type::Null: diff --git a/src/storage/v2/replication/replication_client.cpp b/src/storage/v2/replication/replication_client.cpp index 64cd9b607..c100e2f39 100644 --- a/src/storage/v2/replication/replication_client.cpp +++ b/src/storage/v2/replication/replication_client.cpp @@ -30,9 +30,10 @@ template <typename> } // namespace ////// ReplicationClient ////// -Storage::ReplicationClient::ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint, - const replication::ReplicationMode mode, - const replication::ReplicationClientConfig &config) +InMemoryStorage::ReplicationClient::ReplicationClient(std::string name, InMemoryStorage *storage, + const io::network::Endpoint &endpoint, + const replication::ReplicationMode mode, + const replication::ReplicationClientConfig &config) : name_(std::move(name)), storage_(storage), mode_(mode) { if (config.ssl) { rpc_context_.emplace(config.ssl->key_file, config.ssl->cert_file); @@ -49,14 +50,14 @@ Storage::ReplicationClient::ReplicationClient(std::string name, Storage *storage } } -void Storage::ReplicationClient::TryInitializeClientAsync() { +void InMemoryStorage::ReplicationClient::TryInitializeClientAsync() { thread_pool_.AddTask([this] { rpc_client_->Abort(); this->TryInitializeClientSync(); }); } -void Storage::ReplicationClient::FrequentCheck() { +void InMemoryStorage::ReplicationClient::FrequentCheck() { const auto is_success = std::invoke([this]() { try { auto stream{rpc_client_->Stream<replication::FrequentHeartbeatRpc>()}; @@ -82,7 +83,7 @@ void Storage::ReplicationClient::FrequentCheck() { } /// @throws rpc::RpcFailedException -void Storage::ReplicationClient::InitializeClient() { +void InMemoryStorage::ReplicationClient::InitializeClient() { uint64_t current_commit_timestamp{kTimestampInitialId}; std::optional<std::string> epoch_id; @@ -134,7 +135,7 @@ void Storage::ReplicationClient::InitializeClient() { } } -void Storage::ReplicationClient::TryInitializeClientSync() { +void InMemoryStorage::ReplicationClient::TryInitializeClientSync() { try { InitializeClient(); } catch (const rpc::RpcFailedException &) { @@ -145,19 +146,19 @@ void Storage::ReplicationClient::TryInitializeClientSync() { } } -void Storage::ReplicationClient::HandleRpcFailure() { +void InMemoryStorage::ReplicationClient::HandleRpcFailure() { spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication")); TryInitializeClientAsync(); } -replication::SnapshotRes Storage::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) { +replication::SnapshotRes InMemoryStorage::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) { auto stream{rpc_client_->Stream<replication::SnapshotRpc>()}; replication::Encoder encoder(stream.GetBuilder()); encoder.WriteFile(path); return stream.AwaitResponse(); } -replication::WalFilesRes Storage::ReplicationClient::TransferWalFiles( +replication::WalFilesRes InMemoryStorage::ReplicationClient::TransferWalFiles( const std::vector<std::filesystem::path> &wal_files) { MG_ASSERT(!wal_files.empty(), "Wal files list is empty!"); auto stream{rpc_client_->Stream<replication::WalFilesRpc>(wal_files.size())}; @@ -170,7 +171,7 @@ replication::WalFilesRes Storage::ReplicationClient::TransferWalFiles( return stream.AwaitResponse(); } -void Storage::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) { +void InMemoryStorage::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) { std::unique_lock guard(client_lock_); const auto status = replica_state_.load(); switch (status) { @@ -204,7 +205,8 @@ void Storage::ReplicationClient::StartTransactionReplication(const uint64_t curr } } -void Storage::ReplicationClient::IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback) { +void InMemoryStorage::ReplicationClient::IfStreamingTransaction( + const std::function<void(ReplicaStream &handler)> &callback) { // We can only check the state because it guarantees to be only // valid during a single transaction replication (if the assumption // that this and other transaction replication functions can only be @@ -224,7 +226,7 @@ void Storage::ReplicationClient::IfStreamingTransaction(const std::function<void } } -bool Storage::ReplicationClient::FinalizeTransactionReplication() { +bool InMemoryStorage::ReplicationClient::FinalizeTransactionReplication() { // We can only check the state because it guarantees to be only // valid during a single transaction replication (if the assumption // that this and other transaction replication functions can only be @@ -241,7 +243,7 @@ bool Storage::ReplicationClient::FinalizeTransactionReplication() { } } -bool Storage::ReplicationClient::FinalizeTransactionReplicationInternal() { +bool InMemoryStorage::ReplicationClient::FinalizeTransactionReplicationInternal() { MG_ASSERT(replica_stream_, "Missing stream for transaction deltas"); try { auto response = replica_stream_->Finalize(); @@ -265,7 +267,7 @@ bool Storage::ReplicationClient::FinalizeTransactionReplicationInternal() { return false; } -void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { +void InMemoryStorage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { while (true) { auto file_locker = storage_->file_retainer_.AddLocker(); @@ -327,7 +329,7 @@ void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { } } -uint64_t Storage::ReplicationClient::ReplicateCurrentWal() { +uint64_t InMemoryStorage::ReplicationClient::ReplicateCurrentWal() { const auto &wal_file = storage_->wal_file_; auto stream = TransferCurrentWalFile(); stream.AppendFilename(wal_file->Path().filename()); @@ -361,7 +363,7 @@ uint64_t Storage::ReplicationClient::ReplicateCurrentWal() { /// recovery steps, so we can safely send it to the replica. /// We assume that the property of preserving at least 1 WAL before the snapshot /// is satisfied as we extract the timestamp information from it. -std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient::GetRecoverySteps( +std::vector<InMemoryStorage::ReplicationClient::RecoveryStep> InMemoryStorage::ReplicationClient::GetRecoverySteps( const uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker) { // First check if we can recover using the current wal file only // otherwise save the seq_num of the current wal file @@ -507,8 +509,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient return recovery_steps; } -Storage::TimestampInfo Storage::ReplicationClient::GetTimestampInfo() { - Storage::TimestampInfo info; +InMemoryStorage::TimestampInfo InMemoryStorage::ReplicationClient::GetTimestampInfo() { + InMemoryStorage::TimestampInfo info; info.current_timestamp_of_replica = 0; info.current_number_of_timestamp_behind_master = 0; @@ -536,65 +538,71 @@ Storage::TimestampInfo Storage::ReplicationClient::GetTimestampInfo() { } ////// ReplicaStream ////// -Storage::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self, - const uint64_t previous_commit_timestamp, - const uint64_t current_seq_num) +InMemoryStorage::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self, + const uint64_t previous_commit_timestamp, + const uint64_t current_seq_num) : self_(self), stream_(self_->rpc_client_->Stream<replication::AppendDeltasRpc>(previous_commit_timestamp, current_seq_num)) { replication::Encoder encoder{stream_.GetBuilder()}; encoder.WriteString(self_->storage_->epoch_id_); } -void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, - uint64_t final_commit_timestamp) { +void InMemoryStorage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, + uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, self_->storage_->config_.items, delta, vertex, + EncodeDelta(&encoder, self_->storage_->name_id_mapper_.get(), self_->storage_->config_.items, delta, vertex, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge, - uint64_t final_commit_timestamp) { +void InMemoryStorage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge, + uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, delta, edge, final_commit_timestamp); + EncodeDelta(&encoder, self_->storage_->name_id_mapper_.get(), delta, edge, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) { +void InMemoryStorage::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); EncodeTransactionEnd(&encoder, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation, - LabelId label, const std::set<PropertyId> &properties, - uint64_t timestamp) { +void InMemoryStorage::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation, + LabelId label, + const std::set<PropertyId> &properties, + uint64_t timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, label, properties, timestamp); + EncodeOperation(&encoder, self_->storage_->name_id_mapper_.get(), operation, label, properties, timestamp); } -replication::AppendDeltasRes Storage::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); } +replication::AppendDeltasRes InMemoryStorage::ReplicationClient::ReplicaStream::Finalize() { + return stream_.AwaitResponse(); +} ////// CurrentWalHandler ////// -Storage::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self) +InMemoryStorage::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self) : self_(self), stream_(self_->rpc_client_->Stream<replication::CurrentWalRpc>()) {} -void Storage::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) { +void InMemoryStorage::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteString(filename); } -void Storage::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) { +void InMemoryStorage::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteUint(size); } -void Storage::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) { +void InMemoryStorage::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteFileData(file); } -void Storage::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) { +void InMemoryStorage::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, + const size_t buffer_size) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteBuffer(buffer, buffer_size); } -replication::CurrentWalRes Storage::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); } +replication::CurrentWalRes InMemoryStorage::ReplicationClient::CurrentWalHandler::Finalize() { + return stream_.AwaitResponse(); +} } // namespace memgraph::storage diff --git a/src/storage/v2/replication/replication_client.hpp b/src/storage/v2/replication/replication_client.hpp index 829f0ab60..367c13058 100644 --- a/src/storage/v2/replication/replication_client.hpp +++ b/src/storage/v2/replication/replication_client.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,6 +21,7 @@ #include "storage/v2/delta.hpp" #include "storage/v2/durability/wal.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/name_id_mapper.hpp" #include "storage/v2/property_value.hpp" @@ -28,7 +29,6 @@ #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" @@ -37,9 +37,9 @@ namespace memgraph::storage { -class Storage::ReplicationClient { +class InMemoryStorage::ReplicationClient { public: - ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint, + ReplicationClient(std::string name, InMemoryStorage *storage, const io::network::Endpoint &endpoint, replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {}); // Handler used for transfering the current transaction. @@ -123,7 +123,7 @@ class Storage::ReplicationClient { const auto &Endpoint() const { return rpc_client_->Endpoint(); } - Storage::TimestampInfo GetTimestampInfo(); + InMemoryStorage::TimestampInfo GetTimestampInfo(); private: [[nodiscard]] bool FinalizeTransactionReplicationInternal(); @@ -150,7 +150,7 @@ class Storage::ReplicationClient { void HandleRpcFailure(); std::string name_; - Storage *storage_; + InMemoryStorage *storage_; std::optional<communication::ClientContext> rpc_context_; std::optional<rpc::Client> rpc_client_; diff --git a/src/storage/v2/replication/replication_server.cpp b/src/storage/v2/replication/replication_server.cpp index 8a705460d..035978001 100644 --- a/src/storage/v2/replication/replication_server.cpp +++ b/src/storage/v2/replication/replication_server.cpp @@ -9,18 +9,22 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -#include "storage/v2/replication/replication_server.hpp" #include <atomic> #include <filesystem> #include "spdlog/spdlog.h" + +#include "storage/v2/delta.hpp" #include "storage/v2/durability/durability.hpp" #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/serialization.hpp" #include "storage/v2/durability/snapshot.hpp" #include "storage/v2/durability/version.hpp" #include "storage/v2/durability/wal.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/inmemory/unique_constraints.hpp" #include "storage/v2/replication/config.hpp" +#include "storage/v2/replication/replication_server.hpp" #include "storage/v2/transaction.hpp" #include "utils/exceptions.hpp" @@ -40,8 +44,8 @@ std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder }; } // namespace -Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::Endpoint endpoint, - const replication::ReplicationServerConfig &config) +InMemoryStorage::ReplicationServer::ReplicationServer(InMemoryStorage *storage, io::network::Endpoint endpoint, + const replication::ReplicationServerConfig &config) : storage_(storage) { // Create RPC server. if (config.ssl) { @@ -88,22 +92,21 @@ Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::End rpc_server_->Start(); } -void Storage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void InMemoryStorage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::HeartbeatReq req; slk::Load(&req, req_reader); replication::HeartbeatRes res{true, storage_->last_commit_timestamp_.load(), storage_->epoch_id_}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void InMemoryStorage::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::FrequentHeartbeatReq req; slk::Load(&req, req_reader); replication::FrequentHeartbeatRes res{true}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) { - spdlog::debug("Started replication recovery from appending deltas!"); +void InMemoryStorage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::AppendDeltasReq req; slk::Load(&req, req_reader); @@ -152,8 +155,7 @@ void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, sl spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!"); } -void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) { - spdlog::debug("Started replication recovery from received snapshot file!"); +void InMemoryStorage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::SnapshotReq req; slk::Load(&req, req_reader); @@ -171,14 +173,16 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B storage_->vertices_.clear(); storage_->edges_.clear(); - storage_->constraints_ = Constraints(); - 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); + storage_->constraints_.existence_constraints_ = std::make_unique<ExistenceConstraints>(); + storage_->constraints_.unique_constraints_ = std::make_unique<InMemoryUniqueConstraints>(); + storage_->indices_.label_index_ = + std::make_unique<InMemoryLabelIndex>(&storage_->indices_, &storage_->constraints_, storage_->config_); + storage_->indices_.label_property_index_ = + std::make_unique<InMemoryLabelPropertyIndex>(&storage_->indices_, &storage_->constraints_, storage_->config_); try { spdlog::debug("Loading snapshot"); auto recovered_snapshot = durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, - &storage_->epoch_history_, &storage_->name_id_mapper_, + &storage_->epoch_history_, storage_->name_id_mapper_.get(), &storage_->edge_count_, storage_->config_); spdlog::debug("Snapshot loaded successfully"); // If this step is present it should always be the first step of @@ -224,8 +228,7 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B spdlog::debug("Replication recovery from snapshot finished!"); } -void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) { - spdlog::debug("Started replication recovery from received WAL files!"); +void InMemoryStorage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::WalFilesReq req; slk::Load(&req, req_reader); @@ -245,8 +248,7 @@ void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::B spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!"); } -void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) { - spdlog::debug("Started replication recovery from current WAL!"); +void InMemoryStorage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::CurrentWalReq req; slk::Load(&req, req_reader); @@ -261,7 +263,7 @@ void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk: spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!"); } -void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) { +void InMemoryStorage::ReplicationServer::LoadWal(replication::Decoder *decoder) { const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory; utils::EnsureDir(temp_wal_directory); auto maybe_wal_path = decoder->ReadFile(temp_wal_directory); @@ -306,7 +308,7 @@ void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) { } } -void Storage::ReplicationServer::TimestampHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void InMemoryStorage::ReplicationServer::TimestampHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::TimestampReq req; slk::Load(&req, req_reader); @@ -314,25 +316,29 @@ void Storage::ReplicationServer::TimestampHandler(slk::Reader *req_reader, slk:: slk::Save(res, res_builder); } -Storage::ReplicationServer::~ReplicationServer() { +InMemoryStorage::ReplicationServer::~ReplicationServer() { if (rpc_server_) { rpc_server_->Shutdown(); rpc_server_->AwaitShutdown(); } } -uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) { - spdlog::debug("Reading and applying missing transaction deltas!"); +uint64_t InMemoryStorage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) { auto edge_acc = storage_->edges_.access(); auto vertex_acc = storage_->vertices_.access(); - std::optional<std::pair<uint64_t, storage::Storage::Accessor>> commit_timestamp_and_accessor; + std::optional<std::pair<uint64_t, std::unique_ptr<storage::Storage::Accessor>>> 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()); + commit_timestamp_and_accessor.emplace(commit_timestamp, storage_->Access(std::optional<IsolationLevel>{})); } else if (commit_timestamp_and_accessor->first != commit_timestamp) { throw utils::BasicException("Received more than one transaction!"); } - return &commit_timestamp_and_accessor->second; + // TODO: Rethink this if we would reuse ReplicationServer for on disk storage. + if (auto *inmemoryAcc = + dynamic_cast<storage::InMemoryStorage::InMemoryAccessor *>(commit_timestamp_and_accessor->second.get())) { + return inmemoryAcc; + } + throw utils::BasicException("Received transaction for not supported storage!"); }; uint64_t applied_deltas = 0; @@ -470,6 +476,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * is_visible = true; break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { is_visible = false; break; @@ -503,7 +510,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * spdlog::trace(" 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); + 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; diff --git a/src/storage/v2/replication/replication_server.hpp b/src/storage/v2/replication/replication_server.hpp index 083d1c6cf..6be1ad23c 100644 --- a/src/storage/v2/replication/replication_server.hpp +++ b/src/storage/v2/replication/replication_server.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,13 +11,16 @@ #pragma once -#include "storage/v2/storage.hpp" +#include "rpc/server.hpp" +#include "slk/streams.hpp" +#include "storage/v2/inmemory/storage.hpp" +#include "storage/v2/replication/replication_client.hpp" namespace memgraph::storage { -class Storage::ReplicationServer { +class InMemoryStorage::ReplicationServer { public: - explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint, + explicit ReplicationServer(InMemoryStorage *storage, io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config); ReplicationServer(const ReplicationServer &) = delete; ReplicationServer(ReplicationServer &&) = delete; @@ -42,7 +45,7 @@ class Storage::ReplicationServer { std::optional<communication::ServerContext> rpc_server_context_; std::optional<rpc::Server> rpc_server_; - Storage *storage_; + InMemoryStorage *storage_; }; } // namespace memgraph::storage diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 4c65350b2..8b6555e8a 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -9,49 +9,22 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include "spdlog/spdlog.h" + +#include "storage/v2/disk/name_id_mapper.hpp" #include "storage/v2/storage.hpp" -#include <algorithm> -#include <atomic> -#include <memory> -#include <mutex> -#include <variant> - -#include <gflags/gflags.h> -#include <spdlog/spdlog.h> - -#include "io/network/endpoint.hpp" -#include "storage/v2/durability/durability.hpp" -#include "storage/v2/durability/metadata.hpp" -#include "storage/v2/durability/paths.hpp" -#include "storage/v2/durability/snapshot.hpp" -#include "storage/v2/durability/wal.hpp" -#include "storage/v2/edge_accessor.hpp" -#include "storage/v2/indices.hpp" -#include "storage/v2/mvcc.hpp" -#include "storage/v2/replication/config.hpp" -#include "storage/v2/replication/enums.hpp" -#include "storage/v2/replication/replication_persistence_helper.hpp" -#include "storage/v2/storage_mode.hpp" #include "storage/v2/transaction.hpp" #include "storage/v2/vertex_accessor.hpp" #include "utils/event_counter.hpp" #include "utils/event_histogram.hpp" +#include "utils/exceptions.hpp" #include "utils/file.hpp" #include "utils/logging.hpp" -#include "utils/memory_tracker.hpp" -#include "utils/message.hpp" -#include "utils/rw_lock.hpp" -#include "utils/spin_lock.hpp" #include "utils/stat.hpp" #include "utils/timer.hpp" +#include "utils/typeinfo.hpp" #include "utils/uuid.hpp" -/// REPLICATION /// -#include "storage/v2/replication/replication_client.hpp" -#include "storage/v2/replication/replication_server.hpp" -#include "storage/v2/replication/rpc.hpp" -#include "storage/v2/storage_error.hpp" - namespace memgraph::metrics { extern const Event SnapshotCreationLatency_us; @@ -61,434 +34,23 @@ extern const Event ActiveLabelPropertyIndices; namespace memgraph::storage { +class InMemoryStorage; + using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; -namespace { -inline constexpr uint16_t kEpochHistoryRetention = 1000; - -std::string RegisterReplicaErrorToString(Storage::RegisterReplicaError error) { - switch (error) { - case Storage::RegisterReplicaError::NAME_EXISTS: - return "NAME_EXISTS"; - case Storage::RegisterReplicaError::END_POINT_EXISTS: - return "END_POINT_EXISTS"; - case Storage::RegisterReplicaError::CONNECTION_FAILED: - return "CONNECTION_FAILED"; - case Storage::RegisterReplicaError::COULD_NOT_BE_PERSISTED: - return "COULD_NOT_BE_PERSISTED"; - } -} -} // namespace - -auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end, - std::optional<VertexAccessor> *vertex, Transaction *tx, View view, Indices *indices, - Constraints *constraints, Config::Items config) { - while (it != end) { - *vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view); - if (!*vertex) { - ++it; - continue; - } - break; - } - return it; -} - -AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it) - : self_(self), - it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, - self->indices_, self_->constraints_, self->config_)) {} - -VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } - -AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() { - ++it_; - it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_, - self_->indices_, self_->constraints_, self_->config_); - return *this; -} - -VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) { - new (&all_vertices_) AllVerticesIterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(LabelIndex::Iterable vertices) : type_(Type::BY_LABEL) { - new (&vertices_by_label_) LabelIndex::Iterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(LabelPropertyIndex::Iterable vertices) : type_(Type::BY_LABEL_PROPERTY) { - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) { - switch (other.type_) { - case Type::ALL: - new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); - break; - case Type::BY_LABEL: - new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); - break; - } -} - -VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept { - switch (type_) { - case Type::ALL: - all_vertices_.AllVerticesIterable::~AllVerticesIterable(); - break; - case Type::BY_LABEL: - vertices_by_label_.LabelIndex::Iterable::~Iterable(); - break; - case Type::BY_LABEL_PROPERTY: - vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); - break; - } - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); - break; - case Type::BY_LABEL: - new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); - break; - } - return *this; -} - -VerticesIterable::~VerticesIterable() { - switch (type_) { - case Type::ALL: - all_vertices_.AllVerticesIterable::~AllVerticesIterable(); - break; - case Type::BY_LABEL: - vertices_by_label_.LabelIndex::Iterable::~Iterable(); - break; - case Type::BY_LABEL_PROPERTY: - vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); - break; - } -} - -VerticesIterable::Iterator VerticesIterable::begin() { - switch (type_) { - case Type::ALL: - return Iterator(all_vertices_.begin()); - case Type::BY_LABEL: - return Iterator(vertices_by_label_.begin()); - case Type::BY_LABEL_PROPERTY: - return Iterator(vertices_by_label_property_.begin()); - } -} - -VerticesIterable::Iterator VerticesIterable::end() { - switch (type_) { - case Type::ALL: - return Iterator(all_vertices_.end()); - case Type::BY_LABEL: - return Iterator(vertices_by_label_.end()); - case Type::BY_LABEL_PROPERTY: - return Iterator(vertices_by_label_property_.end()); - } -} - -VerticesIterable::Iterator::Iterator(AllVerticesIterable::Iterator it) : type_(Type::ALL) { - new (&all_it_) AllVerticesIterable::Iterator(std::move(it)); -} - -VerticesIterable::Iterator::Iterator(LabelIndex::Iterable::Iterator it) : type_(Type::BY_LABEL) { - new (&by_label_it_) LabelIndex::Iterable::Iterator(std::move(it)); -} - -VerticesIterable::Iterator::Iterator(LabelPropertyIndex::Iterable::Iterator it) : type_(Type::BY_LABEL_PROPERTY) { - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(std::move(it)); -} - -VerticesIterable::Iterator::Iterator(const VerticesIterable::Iterator &other) : type_(other.type_) { - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const VerticesIterable::Iterator &other) { - Destroy(); - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } - return *this; -} - -VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcept : type_(other.type_) { - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_)); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(std::move(other.by_label_it_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(std::move(other.by_label_property_it_)); - break; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterable::Iterator &&other) noexcept { - Destroy(); - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_)); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(std::move(other.by_label_it_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(std::move(other.by_label_property_it_)); - break; - } - return *this; -} - -VerticesIterable::Iterator::~Iterator() { Destroy(); } - -void VerticesIterable::Iterator::Destroy() noexcept { - switch (type_) { - case Type::ALL: - all_it_.AllVerticesIterable::Iterator::~Iterator(); - break; - case Type::BY_LABEL: - by_label_it_.LabelIndex::Iterable::Iterator::~Iterator(); - break; - case Type::BY_LABEL_PROPERTY: - by_label_property_it_.LabelPropertyIndex::Iterable::Iterator::~Iterator(); - break; - } -} - -VertexAccessor VerticesIterable::Iterator::operator*() const { - switch (type_) { - case Type::ALL: - return *all_it_; - case Type::BY_LABEL: - return *by_label_it_; - case Type::BY_LABEL_PROPERTY: - return *by_label_property_it_; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() { - switch (type_) { - case Type::ALL: - ++all_it_; - break; - case Type::BY_LABEL: - ++by_label_it_; - break; - case Type::BY_LABEL_PROPERTY: - ++by_label_property_it_; - break; - } - return *this; -} - -bool VerticesIterable::Iterator::operator==(const Iterator &other) const { - switch (type_) { - case Type::ALL: - return all_it_ == other.all_it_; - case Type::BY_LABEL: - return by_label_it_ == other.by_label_it_; - case Type::BY_LABEL_PROPERTY: - return by_label_property_it_ == other.by_label_property_it_; - } -} - -Storage::Storage(Config config) - : indices_(&constraints_, config.items), - isolation_level_(config.transaction.isolation_level), - storage_mode_(StorageMode::IN_MEMORY_TRANSACTIONAL), - config_(config), - snapshot_directory_(config_.durability.storage_directory / durability::kSnapshotDirectory), - wal_directory_(config_.durability.storage_directory / durability::kWalDirectory), - lock_file_path_(config_.durability.storage_directory / durability::kLockFile), - uuid_(utils::GenerateUUID()), - epoch_id_(utils::GenerateUUID()), - global_locker_(file_retainer_.AddLocker()) { - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || - config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) { - // Create the directory initially to crash the database in case of - // permission errors. This is done early to crash the database on startup - // instead of crashing the database for the first time during runtime (which - // could be an unpleasant surprise). - utils::EnsureDirOrDie(snapshot_directory_); - // Same reasoning as above. - utils::EnsureDirOrDie(wal_directory_); - - // Verify that the user that started the process is the same user that is - // the owner of the storage directory. - durability::VerifyStorageDirectoryOwnerAndProcessUserOrDie(config_.durability.storage_directory); - - // Create the lock file and open a handle to it. This will crash the - // database if it can't open the file for writing or if any other process is - // holding the file opened. - lock_file_handle_.Open(lock_file_path_, utils::OutputFile::Mode::OVERWRITE_EXISTING); - MG_ASSERT(lock_file_handle_.AcquireLock(), - "Couldn't acquire lock on the storage directory {}" - "!\nAnother Memgraph process is currently running with the same " - "storage directory, please stop it first before starting this " - "process!", - config_.durability.storage_directory); - } - if (config_.durability.recover_on_startup) { - auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, - &vertices_, &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, - config_, &wal_seq_num_); - if (info) { - vertex_id_ = info->next_vertex_id; - edge_id_ = info->next_edge_id; - timestamp_ = std::max(timestamp_, info->next_timestamp); - if (info->last_commit_timestamp) { - last_commit_timestamp_ = *info->last_commit_timestamp; - } - } - } else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || - config_.durability.snapshot_on_exit) { - bool files_moved = false; - auto backup_root = config_.durability.storage_directory / durability::kBackupDirectory; - for (const auto &[path, dirname, what] : - {std::make_tuple(snapshot_directory_, durability::kSnapshotDirectory, "snapshot"), - std::make_tuple(wal_directory_, durability::kWalDirectory, "WAL")}) { - if (!utils::DirExists(path)) continue; - auto backup_curr = backup_root / dirname; - std::error_code error_code; - for (const auto &item : std::filesystem::directory_iterator(path, error_code)) { - utils::EnsureDirOrDie(backup_root); - utils::EnsureDirOrDie(backup_curr); - std::error_code item_error_code; - std::filesystem::rename(item.path(), backup_curr / item.path().filename(), item_error_code); - MG_ASSERT(!item_error_code, "Couldn't move {} file {} because of: {}", what, item.path(), - item_error_code.message()); - files_moved = true; - } - MG_ASSERT(!error_code, "Couldn't backup {} files because of: {}", what, error_code.message()); - } - if (files_moved) { - spdlog::warn( - "Since Memgraph was not supposed to recover on startup and " - "durability is enabled, your current durability files will likely " - "be overridden. To prevent important data loss, Memgraph has stored " - "those files into a .backup directory inside the storage directory."); - } - } - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { - if (auto maybe_error = this->CreateSnapshot({true}); maybe_error.HasError()) { - switch (maybe_error.GetError()) { - case CreateSnapshotError::DisabledForReplica: - spdlog::warn( - utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); - break; - case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: - spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.", - "https://memgr.ph/durability")); - break; - case storage::Storage::CreateSnapshotError::ReachedMaxNumTries: - spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support"); - break; +Storage::Storage(Config config, StorageMode storage_mode) + : name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> { + if (storage_mode == StorageMode::ON_DISK_TRANSACTIONAL) { + return std::make_unique<DiskNameIdMapper>(config.disk.name_id_mapper_directory, + config.disk.id_name_mapper_directory); } - } - }); - } - if (config_.gc.type == Config::Gc::Type::PERIODIC) { - gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->CollectGarbage<false>(); }); - } - - if (timestamp_ == kTimestampInitialId) { - commit_log_.emplace(); - } else { - commit_log_.emplace(timestamp_); - } - - if (config_.durability.restore_replication_state_on_startup) { - spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash."); - utils::EnsureDirOrDie(config_.durability.storage_directory / durability::kReplicationDirectory); - storage_ = - std::make_unique<kvstore::KVStore>(config_.durability.storage_directory / durability::kReplicationDirectory); - - RestoreReplicationRole(); - - if (replication_role_ == replication::ReplicationRole::MAIN) { - RestoreReplicas(); - } - } else { - spdlog::warn( - "Replicastion configuration will NOT be stored. When the server restarts, replication state will be " - "forgotten."); - } - - if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && - replication_role_ == replication::ReplicationRole::MAIN) { - spdlog::warn( - "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider " - "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " - "without write-ahead logs this instance is not replicating any data."); - } -} - -Storage::~Storage() { - if (config_.gc.type == Config::Gc::Type::PERIODIC) { - gc_runner_.Stop(); - } - { - // Clear replication data - replication_server_.reset(); - replication_clients_.WithLock([&](auto &clients) { clients.clear(); }); - } - if (wal_file_) { - wal_file_->FinalizeWal(); - wal_file_ = std::nullopt; - } - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - snapshot_runner_.Stop(); - } - if (config_.durability.snapshot_on_exit) { - if (auto maybe_error = this->CreateSnapshot({false}); maybe_error.HasError()) { - switch (maybe_error.GetError()) { - case CreateSnapshotError::DisabledForReplica: - spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); - break; - case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit: - spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.", - "https://memgr.ph/replication")); - break; - case storage::Storage::CreateSnapshotError::ReachedMaxNumTries: - spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support"); - break; - } - } - } -} + return std::make_unique<NameIdMapper>(); + })), + config_(config), + isolation_level_(config.transaction.isolation_level), + storage_mode_(storage_mode), + indices_(&constraints_, config, storage_mode), + constraints_(config, storage_mode) {} Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode) : storage_(storage), @@ -498,7 +60,7 @@ Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level, St storage_guard_(storage_->main_lock_), transaction_(storage->CreateTransaction(isolation_level, storage_mode)), is_transaction_active_(true), - config_(storage->config_.items) {} + creation_storage_mode_(storage_mode) {} Storage::Accessor::Accessor(Accessor &&other) noexcept : storage_(other.storage_), @@ -506,1817 +68,37 @@ Storage::Accessor::Accessor(Accessor &&other) noexcept transaction_(std::move(other.transaction_)), commit_timestamp_(other.commit_timestamp_), is_transaction_active_(other.is_transaction_active_), - config_(other.config_) { + creation_storage_mode_(other.creation_storage_mode_) { // Don't allow the other accessor to abort our transaction in destructor. other.is_transaction_active_ = false; other.commit_timestamp_.reset(); } -Storage::Accessor::~Accessor() { - if (is_transaction_active_) { - Abort(); - } - - FinalizeTransaction(); -} - -VertexAccessor Storage::Accessor::CreateVertex() { - OOMExceptionEnabler oom_exception; - auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel); - auto acc = storage_->vertices_.access(); - - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta}); - MG_ASSERT(inserted, "The vertex must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - - if (delta) { - delta->prev.Set(&*it); - } - - return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_); -} - -VertexAccessor Storage::Accessor::CreateVertex(storage::Gid gid) { - OOMExceptionEnabler oom_exception; - // NOTE: When we update the next `vertex_id_` here we perform a RMW - // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue - // because this function is only called from the replication delta applier - // that runs single-threadedly and while this instance is set-up to apply - // threads (it is the replica), it is guaranteed that no other writes are - // possible. - storage_->vertex_id_.store(std::max(storage_->vertex_id_.load(std::memory_order_acquire), gid.AsUint() + 1), - std::memory_order_release); - auto acc = storage_->vertices_.access(); - - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Vertex{gid, delta}); - MG_ASSERT(inserted, "The vertex must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - if (delta) { - delta->prev.Set(&*it); - } - return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_); -} - -std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid, View view) { - auto acc = storage_->vertices_.access(); - auto it = acc.find(gid); - if (it == acc.end()) return std::nullopt; - return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, view); -} - -Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) { - MG_ASSERT(vertex->transaction_ == &transaction_, - "VertexAccessor must be from the same transaction as the storage " - "accessor when deleting a vertex!"); - auto *vertex_ptr = vertex->vertex_; - - std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); - - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - if (vertex_ptr->deleted) { - return std::optional<VertexAccessor>{}; - } - - if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) return Error::VERTEX_HAS_EDGES; - - CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); - vertex_ptr->deleted = true; - - // Need to inform the next CollectGarbage call that there are some - // non-transactional deletions that need to be collected - if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { - storage_->gc_full_scan_vertices_delete_ = true; - } - - return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, - config_, true); -} - -Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Storage::Accessor::DetachDeleteVertex( - VertexAccessor *vertex) { - using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>; - - MG_ASSERT(vertex->transaction_ == &transaction_, - "VertexAccessor must be from the same transaction as the storage " - "accessor when deleting a vertex!"); - auto *vertex_ptr = vertex->vertex_; - - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; - - { - std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); - - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - if (vertex_ptr->deleted) return std::optional<ReturnType>{}; - - in_edges = vertex_ptr->in_edges; - out_edges = vertex_ptr->out_edges; - } - - std::vector<EdgeAccessor> deleted_edges; - for (const auto &item : in_edges) { - auto [edge_type, from_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_, - &storage_->constraints_, config_); - auto ret = DeleteEdge(&e); - if (ret.HasError()) { - MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); - return ret.GetError(); - } - - if (ret.GetValue()) { - deleted_edges.push_back(*ret.GetValue()); - } - } - for (const auto &item : out_edges) { - auto [edge_type, to_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, - config_); - auto ret = DeleteEdge(&e); - if (ret.HasError()) { - MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); - return ret.GetError(); - } - - if (ret.GetValue()) { - deleted_edges.push_back(*ret.GetValue()); - } - } - - std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); - - // We need to check again for serialization errors because we unlocked the - // vertex. Some other transaction could have modified the vertex in the - // meantime if we didn't have any edges to delete. - - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); - - CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); - vertex_ptr->deleted = true; - - // Need to inform the next CollectGarbage call that there are some - // non-transactional deletions that need to be collected - if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { - storage_->gc_full_scan_vertices_delete_ = true; - } - - return std::make_optional<ReturnType>( - VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true}, - std::move(deleted_edges)); -} - -Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) { - OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); - - auto from_vertex = from->vertex_; - auto to_vertex = to->vertex_; - - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { - guard_from.lock(); - guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; - } - - auto gid = storage::Gid::FromUint(storage_->edge_id_.fetch_add(1, std::memory_order_acq_rel)); - EdgeRef edge(gid); - if (config_.properties_on_edges) { - auto acc = storage_->edges_.access(); - - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Edge(gid, delta)); - MG_ASSERT(inserted, "The edge must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); - edge = EdgeRef(&*it); - if (delta) { - delta->prev.Set(&*it); - } - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - - // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); - - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, - &storage_->constraints_, config_); -} - -Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, - storage::Gid gid) { - OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); - - auto from_vertex = from->vertex_; - auto to_vertex = to->vertex_; - - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { - guard_from.lock(); - guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; - } - - // NOTE: When we update the next `edge_id_` here we perform a RMW - // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue - // because this function is only called from the replication delta applier - // that runs single-threadedly and while this instance is set-up to apply - // threads (it is the replica), it is guaranteed that no other writes are - // possible. - storage_->edge_id_.store(std::max(storage_->edge_id_.load(std::memory_order_acquire), gid.AsUint() + 1), - std::memory_order_release); - - EdgeRef edge(gid); - if (config_.properties_on_edges) { - auto acc = storage_->edges_.access(); - - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Edge(gid, delta)); - MG_ASSERT(inserted, "The edge must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); - edge = EdgeRef(&*it); - if (delta) { - delta->prev.Set(&*it); - } - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - - // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); - - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, - &storage_->constraints_, config_); -} - -Result<std::optional<EdgeAccessor>> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) { - MG_ASSERT(edge->transaction_ == &transaction_, - "EdgeAccessor must be from the same transaction as the storage " - "accessor when deleting an edge!"); - auto edge_ref = edge->edge_; - auto edge_type = edge->edge_type_; - - std::unique_lock<utils::SpinLock> guard; - if (config_.properties_on_edges) { - auto edge_ptr = edge_ref.ptr; - guard = std::unique_lock<utils::SpinLock>(edge_ptr->lock); - - if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; - - if (edge_ptr->deleted) return std::optional<EdgeAccessor>{}; - } - - auto *from_vertex = edge->from_vertex_; - auto *to_vertex = edge->to_vertex_; - - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { - guard_from.lock(); - guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); - } - - auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { - std::tuple<EdgeTypeId, Vertex *, EdgeRef> link(edge_type, vertex, edge_ref); - auto it = std::find(edges->begin(), edges->end(), link); - if (config_.properties_on_edges) { - MG_ASSERT(it != edges->end(), "Invalid database state!"); - } else if (it == edges->end()) { - return false; - } - std::swap(*it, *edges->rbegin()); - edges->pop_back(); - return true; - }; - - auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); - auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); - - if (config_.properties_on_edges) { - MG_ASSERT((op1 && op2), "Invalid database state!"); - } else { - MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); - if (!op1 && !op2) { - // The edge is already deleted. - return std::optional<EdgeAccessor>{}; - } - } - - if (config_.properties_on_edges) { - auto *edge_ptr = edge_ref.ptr; - CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag()); - edge_ptr->deleted = true; - - // Need to inform the next CollectGarbage call that there are some - // non-transactional deletions that need to be collected - if (transaction_.storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) { - storage_->gc_full_scan_edges_delete_ = true; - } - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); - - // Decrement edge count. - storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); - - return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, - &storage_->indices_, &storage_->constraints_, config_, true); -} - -const std::string &Storage::Accessor::LabelToName(LabelId label) const { return storage_->LabelToName(label); } - -const std::string &Storage::Accessor::PropertyToName(PropertyId property) const { - return storage_->PropertyToName(property); -} - -const std::string &Storage::Accessor::EdgeTypeToName(EdgeTypeId edge_type) const { - return storage_->EdgeTypeToName(edge_type); -} - -LabelId Storage::Accessor::NameToLabel(const std::string_view name) { return storage_->NameToLabel(name); } - -PropertyId Storage::Accessor::NameToProperty(const std::string_view name) { return storage_->NameToProperty(name); } - -EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string_view name) { return storage_->NameToEdgeType(name); } - -void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; } - -utils::BasicResult<StorageDataManipulationError, void> Storage::Accessor::Commit( - const std::optional<uint64_t> desired_commit_timestamp) { - MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); - MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); - - auto could_replicate_all_sync_replicas = true; - - if (transaction_.deltas.empty()) { - // We don't have to update the commit timestamp here because no one reads - // it. - storage_->commit_log_->MarkFinished(transaction_.start_timestamp); - } else { - // Validate that existence constraints are satisfied for all modified - // vertices. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - // No need to take any locks here because we modified this vertex and no - // one else can touch it until we commit. - auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_); - if (validation_result) { - Abort(); - return StorageDataManipulationError{*validation_result}; - } - } - - // Result of validating the vertex against unqiue constraints. It has to be - // declared outside of the critical section scope because its value is - // tested for Abort call which has to be done out of the scope. - std::optional<ConstraintViolation> unique_constraint_violation; - - // Save these so we can mark them used in the commit log. - uint64_t start_timestamp = transaction_.start_timestamp; - - { - std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); - commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp)); - - // Before committing and validating vertices against unique constraints, - // we have to update unique constraints with the vertices that are going - // to be validated/committed. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_); - } - - // Validate that unique constraints are satisfied for all modified - // vertices. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - - // No need to take any locks here because we modified this vertex and no - // one else can touch it until we commit. - unique_constraint_violation = - storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_); - if (unique_constraint_violation) { - break; - } - } - - if (!unique_constraint_violation) { - // Write transaction to WAL while holding the engine lock to make sure - // that committed transactions are sorted by the commit timestamp in the - // WAL files. We supply the new commit timestamp to the function so that - // it knows what will be the final commit timestamp. The WAL must be - // written before actually committing the transaction (before setting - // the commit timestamp) so that no other transaction can see the - // modifications before they are written to disk. - // Replica can log only the write transaction received from Main - // so the Wal files are consistent - if (storage_->replication_role_ == replication::ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { - could_replicate_all_sync_replicas = storage_->AppendToWalDataManipulation(transaction_, *commit_timestamp_); - } - - // Take committed_transactions lock while holding the engine lock to - // make sure that committed transactions are sorted by the commit - // timestamp in the list. - storage_->committed_transactions_.WithLock([&](auto &committed_transactions) { - // TODO: release lock, and update all deltas to have a local copy - // of the commit timestamp - MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); - transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); - // Replica can only update the last commit timestamp with - // the commits received from main. - if (storage_->replication_role_ == replication::ReplicationRole::MAIN || - desired_commit_timestamp.has_value()) { - // Update the last commit timestamp - storage_->last_commit_timestamp_.store(*commit_timestamp_); - } - // Release engine lock because we don't have to hold it anymore - // and emplace back could take a long time. - engine_guard.unlock(); - }); - - storage_->commit_log_->MarkFinished(start_timestamp); - } - } - - if (unique_constraint_violation) { - Abort(); - return StorageDataManipulationError{*unique_constraint_violation}; - } - } - is_transaction_active_ = false; - - if (!could_replicate_all_sync_replicas) { - return StorageDataManipulationError{ReplicationError{}}; - } - - return {}; -} - -void Storage::Accessor::Abort() { - MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); - - // We collect vertices and edges we've created here and then splice them into - // `deleted_vertices_` and `deleted_edges_` lists, instead of adding them one - // by one and acquiring lock every time. - std::list<Gid> my_deleted_vertices; - std::list<Gid> my_deleted_edges; - - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - switch (prev.type) { - case PreviousPtr::Type::VERTEX: { - auto vertex = prev.vertex; - std::lock_guard<utils::SpinLock> guard(vertex->lock); - Delta *current = vertex->delta; - while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == - transaction_.transaction_id.load(std::memory_order_acquire)) { - switch (current->action) { - case Delta::Action::REMOVE_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); - MG_ASSERT(it != vertex->labels.end(), "Invalid database state!"); - std::swap(*it, *vertex->labels.rbegin()); - vertex->labels.pop_back(); - break; - } - case Delta::Action::ADD_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); - MG_ASSERT(it == vertex->labels.end(), "Invalid database state!"); - vertex->labels.push_back(current->label); - break; - } - case Delta::Action::SET_PROPERTY: { - vertex->properties.SetProperty(current->property.key, current->property.value); - break; - } - case Delta::Action::ADD_IN_EDGE: { - std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); - vertex->in_edges.push_back(link); - break; - } - case Delta::Action::ADD_OUT_EDGE: { - std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); - MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); - vertex->out_edges.push_back(link); - // Increment edge count. We only increment the count here because - // the information in `ADD_IN_EDGE` and `Edge/RECREATE_OBJECT` is - // redundant. Also, `Edge/RECREATE_OBJECT` isn't available when - // edge properties are disabled. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); - break; - } - case Delta::Action::REMOVE_IN_EDGE: { - std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - MG_ASSERT(it != vertex->in_edges.end(), "Invalid database state!"); - std::swap(*it, *vertex->in_edges.rbegin()); - vertex->in_edges.pop_back(); - break; - } - case Delta::Action::REMOVE_OUT_EDGE: { - std::tuple<EdgeTypeId, Vertex *, EdgeRef> link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); - MG_ASSERT(it != vertex->out_edges.end(), "Invalid database state!"); - std::swap(*it, *vertex->out_edges.rbegin()); - vertex->out_edges.pop_back(); - // Decrement edge count. We only decrement the count here because - // the information in `REMOVE_IN_EDGE` and `Edge/DELETE_OBJECT` is - // redundant. Also, `Edge/DELETE_OBJECT` isn't available when edge - // properties are disabled. - storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); - break; - } - case Delta::Action::DELETE_OBJECT: { - vertex->deleted = true; - my_deleted_vertices.push_back(vertex->gid); - break; - } - case Delta::Action::RECREATE_OBJECT: { - vertex->deleted = false; - break; - } - } - current = current->next.load(std::memory_order_acquire); - } - vertex->delta = current; - if (current != nullptr) { - current->prev.Set(vertex); - } - - break; - } - case PreviousPtr::Type::EDGE: { - auto edge = prev.edge; - std::lock_guard<utils::SpinLock> guard(edge->lock); - Delta *current = edge->delta; - while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == - transaction_.transaction_id.load(std::memory_order_acquire)) { - switch (current->action) { - case Delta::Action::SET_PROPERTY: { - edge->properties.SetProperty(current->property.key, current->property.value); - break; - } - case Delta::Action::DELETE_OBJECT: { - edge->deleted = true; - my_deleted_edges.push_back(edge->gid); - break; - } - case Delta::Action::RECREATE_OBJECT: { - edge->deleted = false; - break; - } - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: { - LOG_FATAL("Invalid database state!"); - break; - } - } - current = current->next.load(std::memory_order_acquire); - } - edge->delta = current; - if (current != nullptr) { - current->prev.Set(edge); - } - - break; - } - case PreviousPtr::Type::DELTA: - // pointer probably couldn't be set because allocation failed - case PreviousPtr::Type::NULLPTR: - break; - } - } - - { - std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); - uint64_t mark_timestamp = storage_->timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. - storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { - // Release engine lock because we don't have to hold it anymore and - // emplace back could take a long time. - engine_guard.unlock(); - garbage_undo_buffers.emplace_back(mark_timestamp, std::move(transaction_.deltas)); - }); - storage_->deleted_vertices_.WithLock( - [&](auto &deleted_vertices) { deleted_vertices.splice(deleted_vertices.begin(), my_deleted_vertices); }); - storage_->deleted_edges_.WithLock( - [&](auto &deleted_edges) { deleted_edges.splice(deleted_edges.begin(), my_deleted_edges); }); - } - - storage_->commit_log_->MarkFinished(transaction_.start_timestamp); - is_transaction_active_ = false; -} - -void Storage::Accessor::FinalizeTransaction() { - if (commit_timestamp_) { - storage_->commit_log_->MarkFinished(*commit_timestamp_); - storage_->committed_transactions_.WithLock( - [&](auto &committed_transactions) { committed_transactions.emplace_back(std::move(transaction_)); }); - commit_timestamp_.reset(); - } -} - -std::optional<uint64_t> Storage::Accessor::GetTransactionId() const { - if (is_transaction_active_) { - return transaction_.transaction_id.load(std::memory_order_acquire); - } - return {}; -} - -const std::string &Storage::LabelToName(LabelId label) const { return name_id_mapper_.IdToName(label.AsUint()); } - -const std::string &Storage::PropertyToName(PropertyId property) const { - return name_id_mapper_.IdToName(property.AsUint()); -} - -const std::string &Storage::EdgeTypeToName(EdgeTypeId edge_type) const { - return name_id_mapper_.IdToName(edge_type.AsUint()); -} - -LabelId Storage::NameToLabel(const std::string_view name) { return LabelId::FromUint(name_id_mapper_.NameToId(name)); } - -PropertyId Storage::NameToProperty(const std::string_view name) { - return PropertyId::FromUint(name_id_mapper_.NameToId(name)); -} - -EdgeTypeId Storage::NameToEdgeType(const std::string_view name) { - return EdgeTypeId::FromUint(name_id_mapper_.NameToId(name)); -} - -utils::BasicResult<StorageIndexDefinitionError, void> Storage::CreateIndex( - LabelId label, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - if (!indices_.label_index.CreateIndex(label, vertices_.access())) { - return StorageIndexDefinitionError{IndexDefinitionError{}}; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - const auto success = - AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - // We don't care if there is a replication error because on main node the change will go through - memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelIndices); - - if (success) { - return {}; - } - - return StorageIndexDefinitionError{ReplicationError{}}; -} - -utils::BasicResult<StorageIndexDefinitionError, void> Storage::CreateIndex( - LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - if (!indices_.label_property_index.CreateIndex(label, property, vertices_.access())) { - return StorageIndexDefinitionError{IndexDefinitionError{}}; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, - {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - // We don't care if there is a replication error because on main node the change will go through - memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); - - if (success) { - return {}; - } - - return StorageIndexDefinitionError{ReplicationError{}}; -} - -utils::BasicResult<StorageIndexDefinitionError, void> Storage::DropIndex( - LabelId label, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - if (!indices_.label_index.DropIndex(label)) { - return StorageIndexDefinitionError{IndexDefinitionError{}}; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = - AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - // We don't care if there is a replication error because on main node the change will go through - memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelIndices); - - if (success) { - return {}; - } - - return StorageIndexDefinitionError{ReplicationError{}}; -} - -utils::BasicResult<StorageIndexDefinitionError, void> Storage::DropIndex( - LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - if (!indices_.label_property_index.DropIndex(label, property)) { - return StorageIndexDefinitionError{IndexDefinitionError{}}; - } - // For a description why using `timestamp_` is correct, see - // `CreateIndex(LabelId label)`. - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, - {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - // We don't care if there is a replication error because on main node the change will go through - memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelPropertyIndices); - - if (success) { - return {}; - } - - return StorageIndexDefinitionError{ReplicationError{}}; -} - IndicesInfo Storage::ListAllIndices() const { std::shared_lock<utils::RWLock> storage_guard_(main_lock_); - return {indices_.label_index.ListIndices(), indices_.label_property_index.ListIndices()}; -} - -utils::BasicResult<StorageExistenceConstraintDefinitionError, void> Storage::CreateExistenceConstraint( - LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - auto ret = storage::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); - if (ret.HasError()) { - return StorageExistenceConstraintDefinitionError{ret.GetError()}; - } - if (!ret.GetValue()) { - return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}}; - } - - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, - {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - if (success) { - return {}; - } - - return StorageExistenceConstraintDefinitionError{ReplicationError{}}; -} - -utils::BasicResult<StorageExistenceConstraintDroppingError, void> Storage::DropExistenceConstraint( - LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - if (!storage::DropExistenceConstraint(&constraints_, label, property)) { - return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}}; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, - {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - if (success) { - return {}; - } - - return StorageExistenceConstraintDroppingError{ReplicationError{}}; -} - -utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> -Storage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, - const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); - if (ret.HasError()) { - return StorageUniqueConstraintDefinitionError{ret.GetError()}; - } - if (ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { - return ret.GetValue(); - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, - properties, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - if (success) { - return UniqueConstraints::CreationStatus::SUCCESS; - } - - return StorageUniqueConstraintDefinitionError{ReplicationError{}}; -} - -utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> -Storage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, - const std::optional<uint64_t> desired_commit_timestamp) { - std::unique_lock<utils::RWLock> storage_guard(main_lock_); - auto ret = constraints_.unique_constraints.DropConstraint(label, properties); - if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { - return ret; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, - properties, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - - if (success) { - return UniqueConstraints::DeletionStatus::SUCCESS; - } - - return StorageUniqueConstraintDroppingError{ReplicationError{}}; + return {indices_.label_index_->ListIndices(), indices_.label_property_index_->ListIndices()}; } ConstraintsInfo Storage::ListAllConstraints() const { std::shared_lock<utils::RWLock> storage_guard_(main_lock_); - return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()}; + return {constraints_.existence_constraints_->ListConstraints(), constraints_.unique_constraints_->ListConstraints()}; } -StorageInfo Storage::GetInfo() const { - auto vertex_count = vertices_.size(); - auto edge_count = edge_count_.load(std::memory_order_acquire); - double average_degree = 0.0; - if (vertex_count) { - average_degree = 2.0 * static_cast<double>(edge_count) / vertex_count; - } - return {vertex_count, edge_count, average_degree, utils::GetMemoryUsage(), - utils::GetDirDiskUsage(config_.durability.storage_directory)}; -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, View view) { - return VerticesIterable(storage_->indices_.label_index.Vertices(label, view, &transaction_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, View view) { - return VerticesIterable(storage_->indices_.label_property_index.Vertices(label, property, std::nullopt, std::nullopt, - view, &transaction_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, - View view) { - return VerticesIterable(storage_->indices_.label_property_index.Vertices( - label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower_bound, - const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) { - return VerticesIterable( - storage_->indices_.label_property_index.Vertices(label, property, lower_bound, upper_bound, view, &transaction_)); -} - -Transaction Storage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) { - // We acquire the transaction engine lock here because we access (and - // modify) the transaction engine variables (`transaction_id` and - // `timestamp`) below. - uint64_t transaction_id; - uint64_t start_timestamp; - { - std::lock_guard<utils::SpinLock> guard(engine_lock_); - transaction_id = transaction_id_++; - // Replica should have only read queries and the write queries - // can come from main instance with any past timestamp. - // To preserve snapshot isolation we set the start timestamp - // of any query on replica to the last commited transaction - // which is timestamp_ as only commit of transaction with writes - // can change the value of it. - if (replication_role_ == replication::ReplicationRole::REPLICA) { - start_timestamp = timestamp_; - } else { - start_timestamp = timestamp_++; - } - } - return {transaction_id, start_timestamp, isolation_level, storage_mode}; -} - -template <bool force> -void Storage::CollectGarbage(std::unique_lock<utils::RWLock> main_guard) { - // NOTE: You do not need to consider cleanup of deleted object that occurred in - // different storage modes within the same CollectGarbage call. This is because - // SetStorageMode will ensure CollectGarbage is called before any new transactions - // with the new storage mode can start. - - // SetStorageMode will pass its unique_lock of main_lock_. We will use that lock, - // as reacquiring the lock would cause deadlock. Otherwise, we need to get our own - // lock. - if (!main_guard.owns_lock()) { - if constexpr (force) { - // We take the unique lock on the main storage lock, so we can forcefully clean - // everything we can - if (!main_lock_.try_lock()) { - CollectGarbage<false>(); - return; - } - } else { - // Because the garbage collector iterates through the indices and constraints - // to clean them up, it must take the main lock for reading to make sure that - // the indices and constraints aren't concurrently being modified. - main_lock_.lock_shared(); - } - } else { - MG_ASSERT(main_guard.mutex() == std::addressof(main_lock_), "main_guard should be only for the main_lock_"); - } - - utils::OnScopeExit lock_releaser{[&] { - if (!main_guard.owns_lock()) { - if constexpr (force) { - main_lock_.unlock(); - } else { - main_lock_.unlock_shared(); - } - } else { - main_guard.unlock(); - } - }}; - - // Garbage collection must be performed in two phases. In the first phase, - // deltas that won't be applied by any transaction anymore are unlinked from - // the version chains. They cannot be deleted immediately, because there - // might be a transaction that still needs them to terminate the version - // chain traversal. They are instead marked for deletion and will be deleted - // in the second GC phase in this GC iteration or some of the following - // ones. - std::unique_lock<std::mutex> gc_guard(gc_lock_, std::try_to_lock); - if (!gc_guard.owns_lock()) { - return; - } - - uint64_t oldest_active_start_timestamp = commit_log_->OldestActive(); - // We don't move undo buffers of unlinked transactions to garbage_undo_buffers - // list immediately, because we would have to repeatedly take - // garbage_undo_buffers lock. - std::list<std::pair<uint64_t, std::list<Delta>>> unlinked_undo_buffers; - - // We will only free vertices deleted up until now in this GC cycle, and we - // will do it after cleaning-up the indices. That way we are sure that all - // vertices that appear in an index also exist in main storage. - std::list<Gid> current_deleted_edges; - std::list<Gid> current_deleted_vertices; - deleted_vertices_->swap(current_deleted_vertices); - deleted_edges_->swap(current_deleted_edges); - - auto const need_full_scan_vertices = gc_full_scan_vertices_delete_.exchange(false); - auto const need_full_scan_edges = gc_full_scan_edges_delete_.exchange(false); - - // Flag that will be used to determine whether the Index GC should be run. It - // should be run when there were any items that were cleaned up (there were - // updates between this run of the GC and the previous run of the GC). This - // eliminates high CPU usage when the GC doesn't have to clean up anything. - bool run_index_cleanup = !committed_transactions_->empty() || !garbage_undo_buffers_->empty() || - need_full_scan_vertices || need_full_scan_edges; - - while (true) { - // We don't want to hold the lock on committed transactions for too long, - // because that prevents other transactions from committing. - Transaction *transaction; - { - auto committed_transactions_ptr = committed_transactions_.Lock(); - if (committed_transactions_ptr->empty()) { - break; - } - transaction = &committed_transactions_ptr->front(); - } - - auto commit_timestamp = transaction->commit_timestamp->load(std::memory_order_acquire); - if (commit_timestamp >= oldest_active_start_timestamp) { - break; - } - - // When unlinking a delta which is the first delta in its version chain, - // special care has to be taken to avoid the following race condition: - // - // [Vertex] --> [Delta A] - // - // GC thread: Delta A is the first in its chain, it must be unlinked from - // vertex and marked for deletion - // TX thread: Update vertex and add Delta B with Delta A as next - // - // [Vertex] --> [Delta B] <--> [Delta A] - // - // GC thread: Unlink delta from Vertex - // - // [Vertex] --> (nullptr) - // - // When processing a delta that is the first one in its chain, we - // obtain the corresponding vertex or edge lock, and then verify that this - // delta still is the first in its chain. - // When processing a delta that is in the middle of the chain we only - // process the final delta of the given transaction in that chain. We - // determine the owner of the chain (either a vertex or an edge), obtain the - // corresponding lock, and then verify that this delta is still in the same - // position as it was before taking the lock. - // - // Even though the delta chain is lock-free (both `next` and `prev`) the - // chain should not be modified without taking the lock from the object that - // owns the chain (either a vertex or an edge). Modifying the chain without - // taking the lock will cause subtle race conditions that will leave the - // chain in a broken state. - // The chain can be only read without taking any locks. - - for (Delta &delta : transaction->deltas) { - while (true) { - auto prev = delta.prev.Get(); - switch (prev.type) { - case PreviousPtr::Type::VERTEX: { - Vertex *vertex = prev.vertex; - std::lock_guard<utils::SpinLock> vertex_guard(vertex->lock); - if (vertex->delta != &delta) { - // Something changed, we're not the first delta in the chain - // anymore. - continue; - } - vertex->delta = nullptr; - if (vertex->deleted) { - current_deleted_vertices.push_back(vertex->gid); - } - break; - } - case PreviousPtr::Type::EDGE: { - Edge *edge = prev.edge; - std::lock_guard<utils::SpinLock> edge_guard(edge->lock); - if (edge->delta != &delta) { - // Something changed, we're not the first delta in the chain - // anymore. - continue; - } - edge->delta = nullptr; - if (edge->deleted) { - current_deleted_edges.push_back(edge->gid); - } - break; - } - case PreviousPtr::Type::DELTA: { - if (prev.delta->timestamp->load(std::memory_order_acquire) == commit_timestamp) { - // The delta that is newer than this one is also a delta from this - // transaction. We skip the current delta and will remove it as a - // part of the suffix later. - break; - } - std::unique_lock<utils::SpinLock> guard; - { - // We need to find the parent object in order to be able to use - // its lock. - auto parent = prev; - while (parent.type == PreviousPtr::Type::DELTA) { - parent = parent.delta->prev.Get(); - } - switch (parent.type) { - case PreviousPtr::Type::VERTEX: - guard = std::unique_lock<utils::SpinLock>(parent.vertex->lock); - break; - case PreviousPtr::Type::EDGE: - guard = std::unique_lock<utils::SpinLock>(parent.edge->lock); - break; - case PreviousPtr::Type::DELTA: - case PreviousPtr::Type::NULLPTR: - LOG_FATAL("Invalid database state!"); - } - } - if (delta.prev.Get() != prev) { - // Something changed, we could now be the first delta in the - // chain. - continue; - } - Delta *prev_delta = prev.delta; - prev_delta->next.store(nullptr, std::memory_order_release); - break; - } - case PreviousPtr::Type::NULLPTR: { - LOG_FATAL("Invalid pointer!"); - } - } - break; - } - } - - committed_transactions_.WithLock([&](auto &committed_transactions) { - unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); - committed_transactions.pop_front(); - }); - } - - // After unlinking deltas from vertices, we refresh the indices. That way - // we're sure that none of the vertices from `current_deleted_vertices` - // appears in an index, and we can safely remove the from the main storage - // after the last currently active transaction is finished. - if (run_index_cleanup) { - // This operation is very expensive as it traverses through all of the items - // in every index every time. - RemoveObsoleteEntries(&indices_, oldest_active_start_timestamp); - constraints_.unique_constraints.RemoveObsoleteEntries(oldest_active_start_timestamp); - } - - { - std::unique_lock<utils::SpinLock> guard(engine_lock_); - uint64_t mark_timestamp = timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. - garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { - // Release engine lock because we don't have to hold it anymore and - // this could take a long time. - guard.unlock(); - // TODO(mtomic): holding garbage_undo_buffers_ lock here prevents - // transactions from aborting until we're done marking, maybe we should - // add them one-by-one or something - for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { - timestamp = mark_timestamp; - } - garbage_undo_buffers.splice(garbage_undo_buffers.end(), unlinked_undo_buffers); - }); - for (auto vertex : current_deleted_vertices) { - garbage_vertices_.emplace_back(mark_timestamp, vertex); - } - } - - garbage_undo_buffers_.WithLock([&](auto &undo_buffers) { - // if force is set to true we can simply delete all the leftover undos because - // no transaction is active - if constexpr (force) { - undo_buffers.clear(); - } else { - while (!undo_buffers.empty() && undo_buffers.front().first <= oldest_active_start_timestamp) { - undo_buffers.pop_front(); - } - } - }); - - { - auto vertex_acc = vertices_.access(); - if constexpr (force) { - // if force is set to true, then we have unique_lock and no transactions are active - // so we can clean all of the deleted vertices - while (!garbage_vertices_.empty()) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); - garbage_vertices_.pop_front(); - } - } else { - while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); - garbage_vertices_.pop_front(); - } - } - } - { - auto edge_acc = edges_.access(); - for (auto edge : current_deleted_edges) { - MG_ASSERT(edge_acc.remove(edge), "Invalid database state!"); - } - } - - // EXPENSIVE full scan, is only run if an IN_MEMORY_ANALYTICAL transaction involved any deletions - // TODO: implement a fast internal iteration inside the skip_list (to avoid unnecessary find_node calls), - // accessor.remove_if([](auto const & item){ return item.delta == nullptr && item.deleted;}); - // alternatively, an auxiliary data structure within skip_list to track these, hence a full scan wouldn't be needed - // we will wait for evidence that this is needed before doing so. - if (need_full_scan_vertices) { - auto vertex_acc = vertices_.access(); - for (auto &vertex : vertex_acc) { - // a deleted vertex which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion - if (vertex.delta == nullptr && vertex.deleted) { - vertex_acc.remove(vertex); - } - } - } - - // EXPENSIVE full scan, is only run if an IN_MEMORY_ANALYTICAL transaction involved any deletions - if (need_full_scan_edges) { - auto edge_acc = edges_.access(); - for (auto &edge : edge_acc) { - // a deleted edge which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion - if (edge.delta == nullptr && edge.deleted) { - edge_acc.remove(edge); - } - } +/// Main lock is taken by the caller. +void Storage::SetStorageMode(StorageMode storage_mode) { + std::unique_lock main_guard{main_lock_}; + MG_ASSERT( + (storage_mode_ == StorageMode::IN_MEMORY_ANALYTICAL || storage_mode_ == StorageMode::IN_MEMORY_TRANSACTIONAL) && + (storage_mode == StorageMode::IN_MEMORY_ANALYTICAL || storage_mode == StorageMode::IN_MEMORY_TRANSACTIONAL)); + if (storage_mode_ != storage_mode) { + storage_mode_ = storage_mode; + FreeMemory(std::move(main_guard)); } } -// tell the linker it can find the CollectGarbage definitions here -template void Storage::CollectGarbage<true>(std::unique_lock<utils::RWLock>); -template void Storage::CollectGarbage<false>(std::unique_lock<utils::RWLock>); +IsolationLevel Storage::GetIsolationLevel() const noexcept { return isolation_level_; } -bool Storage::InitializeWalFile() { - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) - return false; - if (!wal_file_) { - wal_file_.emplace(wal_directory_, uuid_, epoch_id_, config_.items, &name_id_mapper_, wal_seq_num_++, - &file_retainer_); - } - return true; -} - -void Storage::FinalizeWalFile() { - ++wal_unsynced_transactions_; - if (wal_unsynced_transactions_ >= config_.durability.wal_file_flush_every_n_tx) { - wal_file_->Sync(); - wal_unsynced_transactions_ = 0; - } - if (wal_file_->GetSize() / 1024 >= config_.durability.wal_file_size_kibibytes) { - wal_file_->FinalizeWal(); - wal_file_ = std::nullopt; - wal_unsynced_transactions_ = 0; - } else { - // Try writing the internal buffer if possible, if not - // the data should be written as soon as it's possible - // (triggered by the new transaction commit, or some - // reading thread EnabledFlushing) - wal_file_->TryFlushing(); - } -} - -bool Storage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) { - if (!InitializeWalFile()) { - return true; - } - // Traverse deltas and append them to the WAL file. - // A single transaction will always be contained in a single WAL file. - auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); - - if (replication_role_.load() == replication::ReplicationRole::MAIN) { - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->StartTransactionReplication(wal_file_->SequenceNumber()); - } - }); - } - - // Helper lambda that traverses the delta chain on order to find the first - // delta that should be processed and then appends all discovered deltas. - auto find_and_apply_deltas = [&](const auto *delta, const auto &parent, auto filter) { - while (true) { - auto older = delta->next.load(std::memory_order_acquire); - if (older == nullptr || older->timestamp->load(std::memory_order_acquire) != current_commit_timestamp) break; - delta = older; - } - while (true) { - if (filter(delta->action)) { - wal_file_->AppendDelta(*delta, parent, final_commit_timestamp); - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->IfStreamingTransaction( - [&](auto &stream) { stream.AppendDelta(*delta, parent, final_commit_timestamp); }); - } - }); - } - auto prev = delta->prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::DELTA) break; - delta = prev.delta; - } - }; - - // The deltas are ordered correctly in the `transaction.deltas` buffer, but we - // don't traverse them in that order. That is because for each delta we need - // information about the vertex or edge they belong to and that information - // isn't stored in the deltas themselves. In order to find out information - // about the corresponding vertex or edge it is necessary to traverse the - // delta chain for each delta until a vertex or edge is encountered. This - // operation is very expensive as the chain grows. - // Instead, we traverse the edges until we find a vertex or edge and traverse - // their delta chains. This approach has a drawback because we lose the - // correct order of the operations. Because of that, we need to traverse the - // deltas several times and we have to manually ensure that the stored deltas - // will be ordered correctly. - - // 1. Process all Vertex deltas and store all operations that create vertices - // and modify vertex data. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::DELETE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - return true; - - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - // 2. Process all Vertex deltas and store all operations that create edges. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::REMOVE_OUT_EDGE: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - return false; - } - }); - } - // 3. Process all Edge deltas and store all operations that modify edge data. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::EDGE) continue; - find_and_apply_deltas(&delta, *prev.edge, [](auto action) { - switch (action) { - case Delta::Action::SET_PROPERTY: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - // 4. Process all Vertex deltas and store all operations that delete edges. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::ADD_OUT_EDGE: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - // 5. Process all Vertex deltas and store all operations that delete vertices. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::RECREATE_OBJECT: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - - // Add a delta that indicates that the transaction is fully written to the WAL - // file. - wal_file_->AppendTransactionEnd(final_commit_timestamp); - - FinalizeWalFile(); - - auto finalized_on_all_replicas = true; - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); }); - const auto finalized = client->FinalizeTransactionReplication(); - - if (client->Mode() == replication::ReplicationMode::SYNC) { - finalized_on_all_replicas = finalized && finalized_on_all_replicas; - } - } - }); - - return finalized_on_all_replicas; -} - -bool Storage::AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label, - const std::set<PropertyId> &properties, uint64_t final_commit_timestamp) { - if (!InitializeWalFile()) { - return true; - } - - auto finalized_on_all_replicas = true; - wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp); - { - if (replication_role_.load() == replication::ReplicationRole::MAIN) { - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->StartTransactionReplication(wal_file_->SequenceNumber()); - client->IfStreamingTransaction( - [&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); }); - - const auto finalized = client->FinalizeTransactionReplication(); - if (client->Mode() == replication::ReplicationMode::SYNC) { - finalized_on_all_replicas = finalized && finalized_on_all_replicas; - } - } - }); - } - } - FinalizeWalFile(); - return finalized_on_all_replicas; -} - -utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot(std::optional<bool> is_periodic) { - if (replication_role_.load() != replication::ReplicationRole::MAIN) { - return CreateSnapshotError::DisabledForReplica; - } - - auto snapshot_creator = [this]() { - utils::Timer timer; - - auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_); - // Create snapshot. - durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, - config_.durability.snapshot_retention_count, &vertices_, &edges_, &name_id_mapper_, - &indices_, &constraints_, config_, uuid_, epoch_id_, epoch_history_, &file_retainer_); - // Finalize snapshot transaction. - commit_log_->MarkFinished(transaction.start_timestamp); - - memgraph::metrics::Measure(memgraph::metrics::SnapshotCreationLatency_us, - std::chrono::duration_cast<std::chrono::microseconds>(timer.Elapsed()).count()); - }; - - std::lock_guard snapshot_guard(snapshot_lock_); - - auto should_try_shared{true}; - auto max_num_tries{10}; - while (max_num_tries) { - if (should_try_shared) { - std::shared_lock<utils::RWLock> storage_guard(main_lock_); - if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL) { - snapshot_creator(); - return {}; - } - } else { - std::unique_lock main_guard{main_lock_}; - if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL) { - if (is_periodic && *is_periodic) { - return CreateSnapshotError::DisabledForAnalyticsPeriodicCommit; - } - snapshot_creator(); - return {}; - } - } - should_try_shared = !should_try_shared; - max_num_tries--; - } - - return CreateSnapshotError::ReachedMaxNumTries; -} - -utils::FileRetainer::FileLockerAccessor::ret_type Storage::IsPathLocked() { - auto locker_accessor = global_locker_.Access(); - return locker_accessor.IsPathLocked(config_.durability.storage_directory); -} - -utils::FileRetainer::FileLockerAccessor::ret_type Storage::LockPath() { - auto locker_accessor = global_locker_.Access(); - return locker_accessor.AddPath(config_.durability.storage_directory); -} - -utils::FileRetainer::FileLockerAccessor::ret_type Storage::UnlockPath() { - { - auto locker_accessor = global_locker_.Access(); - const auto ret = locker_accessor.RemovePath(config_.durability.storage_directory); - if (ret.HasError() || !ret.GetValue()) { - // Exit without cleaning the queue - return ret; - } - } - - // We use locker accessor in seperate scope so we don't produce deadlock - // after we call clean queue. - file_retainer_.CleanQueue(); - return true; -} - -void Storage::FreeMemory(std::unique_lock<utils::RWLock> main_guard) { - CollectGarbage<true>(std::move(main_guard)); - - // SkipList is already threadsafe - vertices_.run_gc(); - edges_.run_gc(); - indices_.label_index.RunGC(); - indices_.label_property_index.RunGC(); -} - -uint64_t Storage::CommitTimestamp(const std::optional<uint64_t> desired_commit_timestamp) { - if (!desired_commit_timestamp) { - return timestamp_++; - } else { - timestamp_ = std::max(timestamp_, *desired_commit_timestamp + 1); - return *desired_commit_timestamp; - } -} - -bool Storage::SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config) { - spdlog::trace("Setting role to replica..."); - // We don't want to restart the server if we're already a REPLICA - if (replication_role_ == replication::ReplicationRole::REPLICA) { - return false; - } - - auto port = endpoint.port; // assigning because we will move the endpoint - replication_server_ = std::make_unique<ReplicationServer>(this, std::move(endpoint), config); - - if (ShouldStoreAndRestoreReplicationState()) { - // Only thing that matters here is the role saved as REPLICA and the listening port - auto data = replication::ReplicationStatusToJSON( - replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName, - .ip_address = "", - .port = port, - .sync_mode = replication::ReplicationMode::SYNC, - .replica_check_frequency = std::chrono::seconds(0), - .ssl = std::nullopt, - .role = replication::ReplicationRole::REPLICA}); - - if (!storage_->Put(replication::kReservedReplicationRoleName, data.dump())) { - spdlog::error("Error when saving REPLICA replication role in settings."); - return false; - } - } - - replication_role_.store(replication::ReplicationRole::REPLICA); - return true; -} - -bool Storage::SetMainReplicationRole() { - spdlog::trace("Setting main role..."); - // We don't want to generate new epoch_id and do the - // cleanup if we're already a MAIN - if (replication_role_ == replication::ReplicationRole::MAIN) { - return false; - } - - // Main instance does not need replication server - // This should be always called first so we finalize everything - replication_server_.reset(nullptr); - - { - std::unique_lock engine_guard{engine_lock_}; - if (wal_file_) { - wal_file_->FinalizeWal(); - wal_file_.reset(); - } - - // 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(); - } - - if (ShouldStoreAndRestoreReplicationState()) { - // Only thing that matters here is the role saved as MAIN - auto data = replication::ReplicationStatusToJSON( - replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName, - .ip_address = "", - .port = 0, - .sync_mode = replication::ReplicationMode::SYNC, - .replica_check_frequency = std::chrono::seconds(0), - .ssl = std::nullopt, - .role = replication::ReplicationRole::MAIN}); - - if (!storage_->Put(replication::kReservedReplicationRoleName, data.dump())) { - spdlog::error("Error when saving MAIN replication role in settings."); - return false; - } - } - spdlog::info("Instance is now in a MAIN role."); - replication_role_.store(replication::ReplicationRole::MAIN); - - return true; -} - -utils::BasicResult<Storage::RegisterReplicaError> Storage::RegisterReplica( - std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode, - const replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config) { - - MG_ASSERT(replication_role_.load() == replication::ReplicationRole::MAIN, - "Only main instance can register a replica!"); - spdlog::trace("Registering replica..."); - - const bool name_exists = replication_clients_.WithLock([&](auto &clients) { - return std::any_of(clients.begin(), clients.end(), [&name](const auto &client) { return client->Name() == name; }); - }); - - if (name_exists) { - return RegisterReplicaError::NAME_EXISTS; - } - - const auto end_point_exists = replication_clients_.WithLock([&endpoint](auto &clients) { - return std::any_of(clients.begin(), clients.end(), - [&endpoint](const auto &client) { return client->Endpoint() == endpoint; }); - }); - - if (end_point_exists) { - return RegisterReplicaError::END_POINT_EXISTS; - } - - if (ShouldStoreAndRestoreReplicationState()) { - auto data = replication::ReplicationStatusToJSON( - replication::ReplicationStatus{.name = name, - .ip_address = endpoint.address, - .port = endpoint.port, - .sync_mode = replication_mode, - .replica_check_frequency = config.replica_check_frequency, - .ssl = config.ssl, - .role = replication::ReplicationRole::REPLICA}); - if (!storage_->Put(name, data.dump())) { - spdlog::error("Error when saving replica {} in settings.", name); - return RegisterReplicaError::COULD_NOT_BE_PERSISTED; - } - } - - auto client = std::make_unique<ReplicationClient>(std::move(name), this, endpoint, replication_mode, config); - - if (client->State() == replication::ReplicaState::INVALID) { - if (replication::RegistrationMode::CAN_BE_INVALID != registration_mode) { - return RegisterReplicaError::CONNECTION_FAILED; - } - - spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.", client->Name()); - } - - return replication_clients_.WithLock([&](auto &clients) -> utils::BasicResult<Storage::RegisterReplicaError> { - // Another thread could have added a client with same name while - // we were connecting to this client. - if (std::any_of(clients.begin(), clients.end(), - [&](const auto &other_client) { return client->Name() == other_client->Name(); })) { - return RegisterReplicaError::NAME_EXISTS; - } - - if (std::any_of(clients.begin(), clients.end(), - [&client](const auto &other_client) { return client->Endpoint() == other_client->Endpoint(); })) { - return RegisterReplicaError::END_POINT_EXISTS; - } - - clients.push_back(std::move(client)); - return {}; - }); - spdlog::info("Replica {} registered.", name); -} - -bool Storage::UnregisterReplica(const std::string &name) { - - spdlog::trace("Unregistering replica..."); - MG_ASSERT(replication_role_.load() == replication::ReplicationRole::MAIN, - "Only main instance can unregister a replica!"); - if (ShouldStoreAndRestoreReplicationState()) { - if (!storage_->Delete(name)) { - spdlog::error("Error when removing replica {} from settings.", name); - return false; - } - } - - return replication_clients_.WithLock([&](auto &clients) { - return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); - }); -} - -std::optional<replication::ReplicaState> Storage::GetReplicaState(const std::string_view name) { - spdlog::trace("Getting replica state..."); - return replication_clients_.WithLock([&](auto &clients) -> std::optional<replication::ReplicaState> { - const auto client_it = - std::find_if(clients.cbegin(), clients.cend(), [name](auto &client) { return client->Name() == name; }); - if (client_it == clients.cend()) { - return std::nullopt; - } - return (*client_it)->State(); - }); -} - -replication::ReplicationRole Storage::GetReplicationRole() const { return replication_role_; } - -std::vector<Storage::ReplicaInfo> Storage::ReplicasInfo() { - spdlog::trace("Getting replicas info..."); - return replication_clients_.WithLock([](auto &clients) { - std::vector<Storage::ReplicaInfo> replica_info; - replica_info.reserve(clients.size()); - std::transform( - clients.begin(), clients.end(), std::back_inserter(replica_info), [](const auto &client) -> ReplicaInfo { - return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo()}; - }); - return replica_info; - }); -} +StorageMode Storage::GetStorageMode() const { return storage_mode_; } utils::BasicResult<Storage::SetIsolationLevelError> Storage::SetIsolationLevel(IsolationLevel isolation_level) { std::unique_lock main_guard{main_lock_}; @@ -2328,97 +110,15 @@ utils::BasicResult<Storage::SetIsolationLevelError> Storage::SetIsolationLevel(I return {}; } -void Storage::RestoreReplicationRole() { - if (!ShouldStoreAndRestoreReplicationState()) { - return; +StorageMode Storage::Accessor::GetCreationStorageMode() const { return creation_storage_mode_; } + +std::optional<uint64_t> Storage::Accessor::GetTransactionId() const { + if (is_transaction_active_) { + return transaction_.transaction_id.load(std::memory_order_acquire); } - - spdlog::info("Restoring replication role."); - - uint16_t port = replication::kDefaultReplicationPort; - for (const auto &[replica_name, replica_data] : *storage_) { - const auto maybe_replica_status = replication::JSONToReplicationStatus(nlohmann::json::parse(replica_data)); - if (!maybe_replica_status.has_value()) { - LOG_FATAL("Cannot parse previously saved configuration of replica {}.", replica_name); - } - - if (replica_name != replication::kReservedReplicationRoleName) { - continue; - } - - auto replica_status = *maybe_replica_status; - - if (!replica_status.role.has_value()) { - replication_role_.store(replication::ReplicationRole::MAIN); - } else { - replication_role_.store(*replica_status.role); - port = replica_status.port; - } - - break; - } - - if (replication_role_ == replication::ReplicationRole::REPLICA) { - io::network::Endpoint endpoint(replication::kDefaultReplicationServerIp, port); - replication_server_ = - std::make_unique<ReplicationServer>(this, std::move(endpoint), replication::ReplicationServerConfig{}); - } - - spdlog::info("Replication role restored to {}.", - replication_role_ == replication::ReplicationRole::MAIN ? "MAIN" : "REPLICA"); + return {}; } -IsolationLevel Storage::GetIsolationLevel() const noexcept { return isolation_level_; } - -void Storage::SetStorageMode(StorageMode storage_mode) { - std::unique_lock main_guard{main_lock_}; - // Only if we change storage_mode do we want to force storage cleanup - if (storage_mode_ != storage_mode) { - storage_mode_ = storage_mode; - FreeMemory(std::move(main_guard)); - } -} - -StorageMode Storage::GetStorageMode() { return storage_mode_; } - -void Storage::RestoreReplicas() { - if (!ShouldStoreAndRestoreReplicationState()) { - return; - } - spdlog::info("Restoring replicas."); - - for (const auto &[replica_name, replica_data] : *storage_) { - spdlog::info("Restoring replica {}.", replica_name); - - const auto maybe_replica_status = replication::JSONToReplicationStatus(nlohmann::json::parse(replica_data)); - if (!maybe_replica_status.has_value()) { - LOG_FATAL("Cannot parse previously saved configuration of replica {}.", replica_name); - } - - auto replica_status = *maybe_replica_status; - MG_ASSERT(replica_status.name == replica_name, "Expected replica name is '{}', but got '{}'", replica_status.name, - replica_name); - - if (replica_name == replication::kReservedReplicationRoleName) { - continue; - } - - auto ret = - RegisterReplica(std::move(replica_status.name), {std::move(replica_status.ip_address), replica_status.port}, - replica_status.sync_mode, replication::RegistrationMode::CAN_BE_INVALID, - { - .replica_check_frequency = replica_status.replica_check_frequency, - .ssl = replica_status.ssl, - }); - - if (ret.HasError()) { - MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError()); - LOG_FATAL("Failure when restoring replica {}: {}.", replica_name, RegisterReplicaErrorToString(ret.GetError())); - } - spdlog::info("Replica {} restored.", replica_name); - } -} - -bool Storage::ShouldStoreAndRestoreReplicationState() const { return nullptr != storage_; } +void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; } } // namespace memgraph::storage diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 207ee5290..34985ccad 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -11,176 +11,50 @@ #pragma once -#include <atomic> -#include <cstdint> -#include <filesystem> -#include <optional> -#include <shared_mutex> #include <span> -#include <variant> #include "io/network/endpoint.hpp" #include "kvstore/kvstore.hpp" +#include "query/exceptions.hpp" +#include "storage/v2/all_vertices_iterable.hpp" #include "storage/v2/commit_log.hpp" #include "storage/v2/config.hpp" -#include "storage/v2/constraints.hpp" -#include "storage/v2/durability/metadata.hpp" +#include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/wal.hpp" -#include "storage/v2/edge.hpp" #include "storage/v2/edge_accessor.hpp" -#include "storage/v2/indices.hpp" -#include "storage/v2/isolation_level.hpp" +#include "storage/v2/indices/indices.hpp" #include "storage/v2/mvcc.hpp" -#include "storage/v2/name_id_mapper.hpp" -#include "storage/v2/result.hpp" +#include "storage/v2/storage_error.hpp" #include "storage/v2/storage_mode.hpp" -#include "storage/v2/transaction.hpp" -#include "storage/v2/vertex.hpp" -#include "storage/v2/vertex_accessor.hpp" -#include "utils/file_locker.hpp" -#include "utils/on_scope_exit.hpp" -#include "utils/result.hpp" -#include "utils/rw_lock.hpp" +#include "storage/v2/vertices_iterable.hpp" +#include "utils/event_counter.hpp" +#include "utils/event_histogram.hpp" #include "utils/scheduler.hpp" -#include "utils/skip_list.hpp" -#include "utils/synchronized.hpp" +#include "utils/timer.hpp" #include "utils/uuid.hpp" -/// REPLICATION /// -#include "rpc/server.hpp" -#include "storage/v2/replication/config.hpp" -#include "storage/v2/replication/enums.hpp" -#include "storage/v2/replication/replication_persistence_helper.hpp" -#include "storage/v2/replication/rpc.hpp" -#include "storage/v2/replication/serialization.hpp" -#include "storage/v2/storage_error.hpp" +namespace memgraph::metrics { +extern const Event SnapshotCreationLatency_us; + +extern const Event ActiveLabelIndices; +extern const Event ActiveLabelPropertyIndices; +} // namespace memgraph::metrics namespace memgraph::storage { -// The storage is based on this paper: -// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf -// The paper implements a fully serializable storage, in our implementation we -// only implement snapshot isolation for transactions. +struct Transaction; +class EdgeAccessor; -/// Iterable for iterating through all vertices of a Storage. -/// -/// An instance of this will be usually be wrapped inside VerticesIterable for -/// generic, public use. -class AllVerticesIterable final { - utils::SkipList<Vertex>::Accessor vertices_accessor_; - Transaction *transaction_; - View view_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; - std::optional<VertexAccessor> vertex_; - - public: - class Iterator final { - AllVerticesIterable *self_; - utils::SkipList<Vertex>::Iterator it_; - - public: - Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it); - - VertexAccessor operator*() const; - - Iterator &operator++(); - - bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; } - - bool operator!=(const Iterator &other) const { return !(*this == other); } - }; - - AllVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view, - Indices *indices, Constraints *constraints, Config::Items config) - : vertices_accessor_(std::move(vertices_accessor)), - transaction_(transaction), - view_(view), - indices_(indices), - constraints_(constraints), - config_(config) {} - - Iterator begin() { return Iterator(this, vertices_accessor_.begin()); } - Iterator end() { return Iterator(this, vertices_accessor_.end()); } -}; - -/// Generic access to different kinds of vertex iterations. -/// -/// This class should be the primary type used by the client code to iterate -/// over vertices inside a Storage instance. -class VerticesIterable final { - enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY }; - - Type type_; - union { - AllVerticesIterable all_vertices_; - LabelIndex::Iterable vertices_by_label_; - LabelPropertyIndex::Iterable vertices_by_label_property_; - }; - - public: - explicit VerticesIterable(AllVerticesIterable); - explicit VerticesIterable(LabelIndex::Iterable); - explicit VerticesIterable(LabelPropertyIndex::Iterable); - - VerticesIterable(const VerticesIterable &) = delete; - VerticesIterable &operator=(const VerticesIterable &) = delete; - - VerticesIterable(VerticesIterable &&) noexcept; - VerticesIterable &operator=(VerticesIterable &&) noexcept; - - ~VerticesIterable(); - - class Iterator final { - Type type_; - union { - AllVerticesIterable::Iterator all_it_; - LabelIndex::Iterable::Iterator by_label_it_; - LabelPropertyIndex::Iterable::Iterator by_label_property_it_; - }; - - void Destroy() noexcept; - - public: - explicit Iterator(AllVerticesIterable::Iterator); - explicit Iterator(LabelIndex::Iterable::Iterator); - explicit Iterator(LabelPropertyIndex::Iterable::Iterator); - - Iterator(const Iterator &); - Iterator &operator=(const Iterator &); - - Iterator(Iterator &&) noexcept; - Iterator &operator=(Iterator &&) noexcept; - - ~Iterator(); - - VertexAccessor operator*() const; - - Iterator &operator++(); - - bool operator==(const Iterator &other) const; - bool operator!=(const Iterator &other) const { return !(*this == other); } - }; - - Iterator begin(); - Iterator end(); -}; - -/// Structure used to return information about existing indices in the storage. struct IndicesInfo { std::vector<LabelId> label; std::vector<std::pair<LabelId, PropertyId>> label_property; }; -/// Structure used to return information about existing constraints in the -/// storage. struct ConstraintsInfo { std::vector<std::pair<LabelId, PropertyId>> existence; std::vector<std::pair<LabelId, std::set<PropertyId>>> unique; }; -/// Structure used to return information about the storage. struct StorageInfo { uint64_t vertex_count; uint64_t edge_count; @@ -189,541 +63,244 @@ struct StorageInfo { uint64_t disk_usage; }; -class Storage final { +class Storage { public: - /// @throw std::system_error - /// @throw std::bad_alloc - explicit Storage(Config config = Config()); + Storage(Config config, StorageMode storage_mode); - ~Storage(); + Storage(const Storage &) = delete; + Storage(Storage &&) = delete; + Storage &operator=(const Storage &) = delete; + Storage &operator=(Storage &&) = delete; - class Accessor final { - private: - friend class Storage; - - explicit Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode); + virtual ~Storage() {} + class Accessor { public: + Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode); Accessor(const Accessor &) = delete; Accessor &operator=(const Accessor &) = delete; Accessor &operator=(Accessor &&other) = delete; - // NOTE: After the accessor is moved, all objects derived from it (accessors - // and iterators) are *invalid*. You have to get all derived objects again. Accessor(Accessor &&other) noexcept; - ~Accessor(); + virtual ~Accessor() {} - /// @throw std::bad_alloc - VertexAccessor CreateVertex(); + virtual VertexAccessor CreateVertex() = 0; - std::optional<VertexAccessor> FindVertex(Gid gid, View view); + virtual std::optional<VertexAccessor> FindVertex(Gid gid, View view) = 0; - VerticesIterable Vertices(View view) { - return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view, - &storage_->indices_, &storage_->constraints_, - storage_->config_.items)); - } + virtual VerticesIterable Vertices(View view) = 0; - VerticesIterable Vertices(LabelId label, View view); + virtual VerticesIterable Vertices(LabelId label, View view) = 0; - VerticesIterable Vertices(LabelId label, PropertyId property, View view); + virtual VerticesIterable Vertices(LabelId label, PropertyId property, View view) = 0; - VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view); + virtual VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) = 0; - VerticesIterable Vertices(LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower_bound, - const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view); + virtual VerticesIterable Vertices(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower_bound, + const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) = 0; - /// Return approximate number of all vertices in the database. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount() const { return storage_->vertices_.size(); } + virtual uint64_t ApproximateVertexCount() const = 0; - /// Return approximate number of vertices with the given label. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label) const { - return storage_->indices_.label_index.ApproximateVertexCount(label); - } + virtual uint64_t ApproximateVertexCount(LabelId label) const = 0; - /// Return approximate number of vertices with the given label and property. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property); - } + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property) const = 0; - /// Return approximate number of vertices with the given label and the given - /// value for the given property. Note that this is always an over-estimate - /// and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value); - } + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const = 0; - /// Return approximate number of vertices with the given label and value for - /// the given property in the range defined by provided upper and lower - /// bounds. - int64_t ApproximateVertexCount(LabelId label, PropertyId property, - const std::optional<utils::Bound<PropertyValue>> &lower, - const std::optional<utils::Bound<PropertyValue>> &upper) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper); - } + virtual uint64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional<utils::Bound<PropertyValue>> &lower, + const std::optional<utils::Bound<PropertyValue>> &upper) const = 0; - template <typename TResult, typename TIndex, typename TIndexKey> - std::optional<TResult> GetIndexStatsForIndex(TIndex &index, TIndexKey &&key) const { - return index.GetIndexStats(key); - } + virtual std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const = 0; - std::optional<storage::LabelIndexStats> GetIndexStats(const storage::LabelId &label) const { - return GetIndexStatsForIndex<storage::LabelIndexStats>(storage_->indices_.label_index, label); - } + virtual std::optional<storage::LabelPropertyIndexStats> GetIndexStats( + const storage::LabelId &label, const storage::PropertyId &property) const = 0; - std::optional<storage::LabelPropertyIndexStats> GetIndexStats(const storage::LabelId &label, - const storage::PropertyId &property) const { - return GetIndexStatsForIndex<storage::LabelPropertyIndexStats>(storage_->indices_.label_property_index, - std::make_pair(label, property)); - } + virtual void SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) = 0; - template <typename TIndex, typename TIndexKey, typename TIndexStats> - void SetIndexStatsForIndex(TIndex &index, TIndexKey &&key, TIndexStats &stats) const { - index.SetIndexStats(key, stats); - } + virtual void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property, + const LabelPropertyIndexStats &stats) = 0; - void SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) { - SetIndexStatsForIndex(storage_->indices_.label_index, label, stats); - } + virtual std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() = 0; - void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property, - const LabelPropertyIndexStats &stats) { - SetIndexStatsForIndex(storage_->indices_.label_property_index, std::make_pair(label, property), stats); - } + virtual std::vector<LabelId> ClearLabelIndexStats() = 0; - template <typename TResult, typename TIndex> - std::vector<TResult> ClearIndexStatsForIndex(TIndex &index) const { - return index.ClearIndexStats(); - } + virtual std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats( + std::span<std::string> labels) = 0; - std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() { - return ClearIndexStatsForIndex<std::pair<LabelId, PropertyId>>(storage_->indices_.label_property_index); - } + virtual std::vector<LabelId> DeleteLabelIndexStats(std::span<std::string> labels) = 0; - std::vector<LabelId> ClearLabelIndexStats() { - return ClearIndexStatsForIndex<LabelId>(storage_->indices_.label_index); - } + virtual Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex) = 0; - template <typename TResult, typename TIndex> - std::vector<TResult> DeleteIndexStatsForIndex(TIndex &index, const std::span<std::string> labels) { - std::vector<TResult> deleted_indexes; + virtual Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex( + VertexAccessor *vertex) = 0; - for (const auto &label : labels) { - std::vector<TResult> loc_results = index.DeleteIndexStats(NameToLabel(label)); - deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()), - std::make_move_iterator(loc_results.end())); - } - return deleted_indexes; - } + virtual void PrefetchInEdges(const VertexAccessor &vertex_acc) = 0; - std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats(const std::span<std::string> labels) { - return DeleteIndexStatsForIndex<std::pair<LabelId, PropertyId>>(storage_->indices_.label_property_index, labels); - } + virtual void PrefetchOutEdges(const VertexAccessor &vertex_acc) = 0; - std::vector<LabelId> DeleteLabelIndexStats(const std::span<std::string> labels) { - return DeleteIndexStatsForIndex<LabelId>(storage_->indices_.label_index, labels); - } + virtual Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) = 0; - /// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex); + virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge) = 0; - /// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex( - VertexAccessor *vertex); + virtual bool LabelIndexExists(LabelId label) const = 0; - /// @throw std::bad_alloc - Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type); + virtual bool LabelPropertyIndexExists(LabelId label, PropertyId property) const = 0; - /// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge); + virtual IndicesInfo ListAllIndices() const = 0; - const std::string &LabelToName(LabelId label) const; - const std::string &PropertyToName(PropertyId property) const; - const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; + virtual ConstraintsInfo ListAllConstraints() const = 0; - /// @throw std::bad_alloc if unable to insert a new mapping - LabelId NameToLabel(std::string_view name); + // NOLINTNEXTLINE(google-default-arguments) + virtual utils::BasicResult<StorageDataManipulationError, void> Commit( + std::optional<uint64_t> desired_commit_timestamp = {}) = 0; - /// @throw std::bad_alloc if unable to insert a new mapping - PropertyId NameToProperty(std::string_view name); + virtual void Abort() = 0; - /// @throw std::bad_alloc if unable to insert a new mapping - EdgeTypeId NameToEdgeType(std::string_view name); - - bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); } - - bool LabelPropertyIndexExists(LabelId label, PropertyId property) const { - return storage_->indices_.label_property_index.IndexExists(label, property); - } - - IndicesInfo ListAllIndices() const { - return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()}; - } - - ConstraintsInfo ListAllConstraints() const { - return {ListExistenceConstraints(storage_->constraints_), - storage_->constraints_.unique_constraints.ListConstraints()}; - } - - void AdvanceCommand(); - - /// Returns void if the transaction has been committed. - /// Returns `StorageDataManipulationError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this - /// case the transaction is automatically aborted. - /// @throw std::bad_alloc - utils::BasicResult<StorageDataManipulationError, void> Commit( - std::optional<uint64_t> desired_commit_timestamp = {}); - - /// @throw std::bad_alloc - void Abort(); - - void FinalizeTransaction(); + virtual void FinalizeTransaction() = 0; std::optional<uint64_t> GetTransactionId() const; - private: - /// @throw std::bad_alloc - VertexAccessor CreateVertex(storage::Gid gid); + void AdvanceCommand(); - /// @throw std::bad_alloc - Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid); + const std::string &LabelToName(LabelId label) const { return storage_->LabelToName(label); } + const std::string &PropertyToName(PropertyId property) const { return storage_->PropertyToName(property); } + + const std::string &EdgeTypeToName(EdgeTypeId edge_type) const { return storage_->EdgeTypeToName(edge_type); } + + LabelId NameToLabel(std::string_view name) { return storage_->NameToLabel(name); } + + PropertyId NameToProperty(std::string_view name) { return storage_->NameToProperty(name); } + + EdgeTypeId NameToEdgeType(std::string_view name) { return storage_->NameToEdgeType(name); } + + StorageMode GetCreationStorageMode() const; + + protected: Storage *storage_; std::shared_lock<utils::RWLock> storage_guard_; Transaction transaction_; std::optional<uint64_t> commit_timestamp_; bool is_transaction_active_; - Config::Items config_; + + private: + StorageMode creation_storage_mode_; }; - Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) { - return Accessor{this, override_isolation_level.value_or(isolation_level_), storage_mode_}; + const std::string &LabelToName(LabelId label) const { return name_id_mapper_->IdToName(label.AsUint()); } + + const std::string &PropertyToName(PropertyId property) const { return name_id_mapper_->IdToName(property.AsUint()); } + + const std::string &EdgeTypeToName(EdgeTypeId edge_type) const { + return name_id_mapper_->IdToName(edge_type.AsUint()); } - const std::string &LabelToName(LabelId label) const; - const std::string &PropertyToName(PropertyId property) const; - const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; + LabelId NameToLabel(const std::string_view name) const { return LabelId::FromUint(name_id_mapper_->NameToId(name)); } - /// @throw std::bad_alloc if unable to insert a new mapping - LabelId NameToLabel(std::string_view name); + PropertyId NameToProperty(const std::string_view name) const { + return PropertyId::FromUint(name_id_mapper_->NameToId(name)); + } - /// @throw std::bad_alloc if unable to insert a new mapping - PropertyId NameToProperty(std::string_view name); + EdgeTypeId NameToEdgeType(const std::string_view name) const { + return EdgeTypeId::FromUint(name_id_mapper_->NameToId(name)); + } - /// @throw std::bad_alloc if unable to insert a new mapping - EdgeTypeId NameToEdgeType(std::string_view name); + void SetStorageMode(StorageMode storage_mode); - /// Create an index. - /// Returns void if the index has been created. - /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: - /// * `IndexDefinitionError`: the index already exists. - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// @throw std::bad_alloc - utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( - LabelId label, std::optional<uint64_t> desired_commit_timestamp = {}); + StorageMode GetStorageMode() const; - /// Create an index. - /// Returns void if the index has been created. - /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `IndexDefinitionError`: the index already exists. - /// @throw std::bad_alloc - utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( - LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual void FreeMemory(std::unique_lock<utils::RWLock> main_guard) = 0; + void FreeMemory() { FreeMemory({}); } - /// Drop an existing index. - /// Returns void if the index has been dropped. - /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `IndexDefinitionError`: the index does not exist. - utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( - LabelId label, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) = 0; + std::unique_ptr<Accessor> Access() { return Access(std::optional<IsolationLevel>{}); } - /// Drop an existing index. - /// Returns void if the index has been dropped. - /// Returns `StorageIndexDefinitionError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `IndexDefinitionError`: the index does not exist. - utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( - LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) = 0; + + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) { + return CreateIndex(label, std::optional<uint64_t>{}); + } + + virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0; + + utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) { + return CreateIndex(label, property, std::optional<uint64_t>{}); + } + + virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, std::optional<uint64_t> desired_commit_timestamp) = 0; + + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) { + return DropIndex(label, std::optional<uint64_t>{}); + } + + virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0; + + utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) { + return DropIndex(label, property, std::optional<uint64_t>{}); + } IndicesInfo ListAllIndices() const; - /// Returns void if the existence constraint has been created. - /// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint. - /// * `ConstraintDefinitionError`: the constraint already exists. - /// @throw std::bad_alloc - /// @throw std::length_error - utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( - LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0; - /// Drop an existing existence constraint. - /// Returns void if the existence constraint has been dropped. - /// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `ConstraintDefinitionError`: the constraint did not exists. - utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint( - LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint( + LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0; - /// Create an unique constraint. - /// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// * `ConstraintViolation`: there are already vertices violating the constraint. - /// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be: - /// * `SUCCESS` if the constraint was successfully created, - /// * `ALREADY_EXISTS` if the constraint already existed, - /// * `EMPTY_PROPERTIES` if the property set is empty, or - /// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties. - /// @throw std::bad_alloc - utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint( - LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> + CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + std::optional<uint64_t> desired_commit_timestamp) = 0; - /// Removes an existing unique constraint. - /// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be: - /// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction. - /// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be: - /// * `SUCCESS` if constraint was successfully removed, - /// * `NOT_FOUND` if the specified constraint was not found, - /// * `EMPTY_PROPERTIES` if the property set is empty, or - /// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties. - utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint( - LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {}); + virtual utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> + DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties, + std::optional<uint64_t> desired_commit_timestamp) = 0; ConstraintsInfo ListAllConstraints() const; - StorageInfo GetInfo() const; - - utils::FileRetainer::FileLockerAccessor::ret_type IsPathLocked(); - utils::FileRetainer::FileLockerAccessor::ret_type LockPath(); - utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath(); - - bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {}); - - bool SetMainReplicationRole(); - - enum class RegisterReplicaError : uint8_t { - NAME_EXISTS, - END_POINT_EXISTS, - CONNECTION_FAILED, - COULD_NOT_BE_PERSISTED - }; - - /// @pre The instance should have a MAIN role - /// @pre Timeout can only be set for SYNC replication - utils::BasicResult<RegisterReplicaError, void> RegisterReplica( - std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode, - replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config = {}); - /// @pre The instance should have a MAIN role - bool UnregisterReplica(const std::string &name); - - std::optional<replication::ReplicaState> GetReplicaState(std::string_view name); - - replication::ReplicationRole GetReplicationRole() const; - - struct TimestampInfo { - uint64_t current_timestamp_of_replica; - uint64_t current_number_of_timestamp_behind_master; - }; - - struct ReplicaInfo { - std::string name; - replication::ReplicationMode mode; - io::network::Endpoint endpoint; - replication::ReplicaState state; - TimestampInfo timestamp_info; - }; - - std::vector<ReplicaInfo> ReplicasInfo(); - - void FreeMemory(std::unique_lock<utils::RWLock> main_guard = {}); - enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode }; utils::BasicResult<SetIsolationLevelError> SetIsolationLevel(IsolationLevel isolation_level); IsolationLevel GetIsolationLevel() const noexcept; - void SetStorageMode(StorageMode storage_mode); + virtual StorageInfo GetInfo() const = 0; - StorageMode GetStorageMode(); - - enum class CreateSnapshotError : uint8_t { - DisabledForReplica, - DisabledForAnalyticsPeriodicCommit, - ReachedMaxNumTries - }; - - utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic); - - private: - Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode); - - /// The force parameter determines the behaviour of the garbage collector. - /// If it's set to true, it will behave as a global operation, i.e. it can't - /// be part of a transaction, and no other transaction can be active at the same time. - /// This allows it to delete immediately vertices without worrying that some other - /// transaction is possibly using it. If there are active transactions when this method - /// is called with force set to true, it will fallback to the same method with the force - /// set to false. - /// If it's set to false, it will execute in parallel with other transactions, ensuring - /// that no object in use can be deleted. - /// @throw std::system_error - /// @throw std::bad_alloc - template <bool force> - void CollectGarbage(std::unique_lock<utils::RWLock> main_guard = {}); - - bool InitializeWalFile(); - void FinalizeWalFile(); - - /// Return true in all cases excepted if any sync replicas have not sent confirmation. - [[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp); - /// Return true in all cases excepted if any sync replicas have not sent confirmation. - [[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label, - const std::set<PropertyId> &properties, uint64_t final_commit_timestamp); - - uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {}); - - void RestoreReplicationRole(); - - void RestoreReplicas(); - - bool ShouldStoreAndRestoreReplicationState() const; + virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) = 0; // Main storage lock. - // // Accessors take a shared lock when starting, so it is possible to block // creation of new accessors by taking a unique lock. This is used when doing // operations on storage that affect the global state, for example index // creation. mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE}; - // Main object storage - utils::SkipList<storage::Vertex> vertices_; - utils::SkipList<storage::Edge> edges_; - std::atomic<uint64_t> vertex_id_{0}; - std::atomic<uint64_t> edge_id_{0}; // Even though the edge count is already kept in the `edges_` SkipList, the // list is used only when properties are enabled for edges. Because of that we // keep a separate count of edges that is always updated. std::atomic<uint64_t> edge_count_{0}; - NameIdMapper name_id_mapper_; - - Constraints constraints_; - Indices indices_; + std::unique_ptr<NameIdMapper> name_id_mapper_; + Config config_; // Transaction engine utils::SpinLock engine_lock_; uint64_t timestamp_{kTimestampInitialId}; uint64_t transaction_id_{kTransactionInitialId}; - // TODO: This isn't really a commit log, it doesn't even care if a - // transaction commited or aborted. We could probably combine this with - // `timestamp_` in a sensible unit, something like TransactionClock or - // whatever. - std::optional<CommitLog> commit_log_; - utils::Synchronized<std::list<Transaction>, utils::SpinLock> committed_transactions_; IsolationLevel isolation_level_; StorageMode storage_mode_; - Config config_; - utils::Scheduler gc_runner_; - std::mutex gc_lock_; + Indices indices_; + Constraints constraints_; - // Undo buffers that were unlinked and now are waiting to be freed. - utils::Synchronized<std::list<std::pair<uint64_t, std::list<Delta>>>, utils::SpinLock> garbage_undo_buffers_; - - // Vertices that are logically deleted but still have to be removed from - // indices before removing them from the main storage. - utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_; - - // Vertices that are logically deleted and removed from indices and now wait - // to be removed from the main storage. - std::list<std::pair<uint64_t, Gid>> garbage_vertices_; - - // Edges that are logically deleted and wait to be removed from the main - // storage. - utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_; - - // Flags to inform CollectGarbage that it needs to do the more expensive full scans - std::atomic<bool> gc_full_scan_vertices_delete_ = false; - std::atomic<bool> gc_full_scan_edges_delete_ = false; - - // Durability - std::filesystem::path snapshot_directory_; - std::filesystem::path wal_directory_; - std::filesystem::path lock_file_path_; - utils::OutputFile lock_file_handle_; - std::unique_ptr<kvstore::KVStore> storage_; - - utils::Scheduler snapshot_runner_; - utils::SpinLock snapshot_lock_; - - // UUID used to distinguish snapshots and to link snapshots to WALs - std::string uuid_; - // 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<std::pair<std::string, uint64_t>> epoch_history_; - - std::optional<durability::WalFile> wal_file_; - uint64_t wal_unsynced_transactions_{0}; - - utils::FileRetainer file_retainer_; - - // Global locker that is used for clients file locking - utils::FileRetainer::FileLocker global_locker_; - - // Last commited timestamp - std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId}; - - class ReplicationServer; - std::unique_ptr<ReplicationServer> 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 initialize client in main thread which means - // that we can immediately notify the user if the initialization - // failed. - using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>; - ReplicationClientList replication_clients_; - - std::atomic<replication::ReplicationRole> replication_role_{replication::ReplicationRole::MAIN}; + std::atomic<uint64_t> vertex_id_{0}; + std::atomic<uint64_t> edge_id_{0}; }; } // namespace memgraph::storage diff --git a/src/storage/v2/storage_error.hpp b/src/storage/v2/storage_error.hpp index 1e071f748..b9ce0fb71 100644 --- a/src/storage/v2/storage_error.hpp +++ b/src/storage/v2/storage_error.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,28 +11,38 @@ #pragma once -#include "storage/v2/constraints.hpp" +#include "storage/v2/constraints/constraints.hpp" +#include <iterator> #include <variant> namespace memgraph::storage { struct ReplicationError {}; -using StorageDataManipulationError = std::variant<ConstraintViolation, ReplicationError>; +struct IndexPersistenceError {}; + +struct ConstraintsPersistenceError {}; + +struct SerializationError {}; +inline bool operator==(const SerializationError & /*err1*/, const SerializationError & /*err2*/) { return true; } + +using StorageDataManipulationError = std::variant<ConstraintViolation, ReplicationError, SerializationError>; struct IndexDefinitionError {}; -using StorageIndexDefinitionError = std::variant<IndexDefinitionError, ReplicationError>; +using StorageIndexDefinitionError = std::variant<IndexDefinitionError, ReplicationError, IndexPersistenceError>; struct ConstraintDefinitionError {}; using StorageExistenceConstraintDefinitionError = - std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError>; + std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>; -using StorageExistenceConstraintDroppingError = std::variant<ConstraintDefinitionError, ReplicationError>; +using StorageExistenceConstraintDroppingError = + std::variant<ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>; -using StorageUniqueConstraintDefinitionError = std::variant<ConstraintViolation, ReplicationError>; +using StorageUniqueConstraintDefinitionError = + std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>; -using StorageUniqueConstraintDroppingError = std::variant<ReplicationError>; +using StorageUniqueConstraintDroppingError = std::variant<ReplicationError, ConstraintsPersistenceError>; } // namespace memgraph::storage diff --git a/src/storage/v2/storage_mode.cpp b/src/storage/v2/storage_mode.cpp index a05017f1d..73a886d6a 100644 --- a/src/storage/v2/storage_mode.cpp +++ b/src/storage/v2/storage_mode.cpp @@ -19,6 +19,8 @@ std::string_view StorageModeToString(memgraph::storage::StorageMode storage_mode return "IN_MEMORY_ANALYTICAL"; case memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL: return "IN_MEMORY_TRANSACTIONAL"; + case memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL: + return "ON_DISK_TRANSACTIONAL"; } } diff --git a/src/storage/v2/storage_mode.hpp b/src/storage/v2/storage_mode.hpp index 2da6435cd..2ab348c59 100644 --- a/src/storage/v2/storage_mode.hpp +++ b/src/storage/v2/storage_mode.hpp @@ -16,7 +16,7 @@ namespace memgraph::storage { -enum class StorageMode : std::uint8_t { IN_MEMORY_ANALYTICAL, IN_MEMORY_TRANSACTIONAL }; +enum class StorageMode : std::uint8_t { IN_MEMORY_ANALYTICAL, IN_MEMORY_TRANSACTIONAL, ON_DISK_TRANSACTIONAL }; std::string_view StorageModeToString(memgraph::storage::StorageMode storage_mode); diff --git a/src/storage/v2/vertex.hpp b/src/storage/v2/vertex.hpp index 83f517c46..5ec612f68 100644 --- a/src/storage/v2/vertex.hpp +++ b/src/storage/v2/vertex.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,7 +25,8 @@ namespace memgraph::storage { struct Vertex { Vertex(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, + MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT || + delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp index 9682565f9..9bf3c8fa9 100644 --- a/src/storage/v2/vertex_accessor.cpp +++ b/src/storage/v2/vertex_accessor.cpp @@ -15,7 +15,7 @@ #include "storage/v2/edge_accessor.hpp" #include "storage/v2/id_types.hpp" -#include "storage/v2/indices.hpp" +#include "storage/v2/indices/indices.hpp" #include "storage/v2/mvcc.hpp" #include "storage/v2/property_value.hpp" #include "utils/logging.hpp" @@ -48,6 +48,7 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v deleted = false; break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -79,34 +80,37 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) { std::lock_guard<utils::SpinLock> guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; - if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false; CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label); - vertex_->labels.push_back(label); - UpdateOnAddLabel(indices_, label, vertex_, *transaction_); + /// TODO: some by pointers, some by reference => not good, make it better + constraints_->unique_constraints_->UpdateOnAddLabel(label, *vertex_, transaction_->start_timestamp); + indices_->UpdateOnAddLabel(label, vertex_, *transaction_); return true; } +/// TODO: move to after update and change naming to vertex after update Result<bool> VertexAccessor::RemoveLabel(LabelId label) { std::lock_guard<utils::SpinLock> guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label); if (it == vertex_->labels.end()) return false; CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label); - std::swap(*it, *vertex_->labels.rbegin()); vertex_->labels.pop_back(); + + /// TODO: some by pointers, some by reference => not good, make it better + constraints_->unique_constraints_->UpdateOnRemoveLabel(label, *vertex_, transaction_->start_timestamp); + indices_->UpdateOnRemoveLabel(label, vertex_, *transaction_); + return true; } @@ -137,6 +141,7 @@ Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const { } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -186,6 +191,7 @@ Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const { labels.push_back(delta.label); break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -226,7 +232,7 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value); vertex_->properties.SetProperty(property, value); - UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_); + indices_->UpdateOnSetProperty(property, value, vertex_, *transaction_); return std::move(current_value); } @@ -242,7 +248,7 @@ Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId, if (!vertex_->properties.InitProperties(properties)) return false; for (const auto &[property, value] : properties) { CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue()); - UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_); + indices_->UpdateOnSetProperty(property, value, vertex_, *transaction_); } return true; @@ -258,7 +264,7 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() { auto properties = vertex_->properties.Properties(); for (const auto &property : properties) { CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property.first, property.second); - UpdateOnSetProperty(indices_, property.first, PropertyValue(), vertex_, *transaction_); + indices_->UpdateOnSetProperty(property.first, PropertyValue(), vertex_, *transaction_); } vertex_->properties.ClearProperties(); @@ -285,6 +291,7 @@ Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -335,6 +342,7 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view } break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -410,6 +418,7 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std:: in_edges.pop_back(); break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -490,6 +499,7 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std: out_edges.pop_back(); break; } + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { exists = false; break; @@ -536,6 +546,7 @@ Result<size_t> VertexAccessor::InDegree(View view) const { case Delta::Action::REMOVE_IN_EDGE: --degree; break; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: exists = false; break; @@ -574,6 +585,7 @@ Result<size_t> VertexAccessor::OutDegree(View view) const { case Delta::Action::REMOVE_OUT_EDGE: --degree; break; + case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: exists = false; break; diff --git a/src/storage/v2/vertex_accessor.hpp b/src/storage/v2/vertex_accessor.hpp index 79a391708..db1a6a6ef 100644 --- a/src/storage/v2/vertex_accessor.hpp +++ b/src/storage/v2/vertex_accessor.hpp @@ -24,8 +24,8 @@ namespace memgraph::storage { class EdgeAccessor; class Storage; -struct Indices; struct Constraints; +struct Indices; class VertexAccessor final { private: @@ -106,7 +106,6 @@ class VertexAccessor final { } bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); } - private: Vertex *vertex_; Transaction *transaction_; Indices *indices_; diff --git a/src/storage/v2/vertices_iterable.cpp b/src/storage/v2/vertices_iterable.cpp new file mode 100644 index 000000000..34d6c76b2 --- /dev/null +++ b/src/storage/v2/vertices_iterable.cpp @@ -0,0 +1,250 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "storage/v2/vertices_iterable.hpp" + +namespace memgraph::storage { + +VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) { + new (&all_vertices_) AllVerticesIterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(InMemoryLabelIndex::Iterable vertices) : type_(Type::BY_LABEL_IN_MEMORY) { + new (&in_memory_vertices_by_label_) InMemoryLabelIndex::Iterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(InMemoryLabelPropertyIndex::Iterable vertices) + : type_(Type::BY_LABEL_PROPERTY_IN_MEMORY) { + new (&in_memory_vertices_by_label_property_) InMemoryLabelPropertyIndex::Iterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::ALL: + new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); + break; + case Type::BY_LABEL_IN_MEMORY: + new (&in_memory_vertices_by_label_) InMemoryLabelIndex::Iterable(std::move(other.in_memory_vertices_by_label_)); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_vertices_by_label_property_) + InMemoryLabelPropertyIndex::Iterable(std::move(other.in_memory_vertices_by_label_property_)); + break; + } +} + +VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept { + switch (type_) { + case Type::ALL: + all_vertices_.AllVerticesIterable::~AllVerticesIterable(); + break; + case Type::BY_LABEL_IN_MEMORY: + in_memory_vertices_by_label_.InMemoryLabelIndex::Iterable::~Iterable(); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + in_memory_vertices_by_label_property_.InMemoryLabelPropertyIndex::Iterable::~Iterable(); + break; + } + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); + break; + case Type::BY_LABEL_IN_MEMORY: + new (&in_memory_vertices_by_label_) InMemoryLabelIndex::Iterable(std::move(other.in_memory_vertices_by_label_)); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_vertices_by_label_property_) + InMemoryLabelPropertyIndex::Iterable(std::move(other.in_memory_vertices_by_label_property_)); + break; + } + return *this; +} + +VerticesIterable::~VerticesIterable() { + switch (type_) { + case Type::ALL: + all_vertices_.AllVerticesIterable::~AllVerticesIterable(); + break; + case Type::BY_LABEL_IN_MEMORY: + in_memory_vertices_by_label_.InMemoryLabelIndex::Iterable::~Iterable(); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + in_memory_vertices_by_label_property_.InMemoryLabelPropertyIndex::Iterable::~Iterable(); + break; + } +} + +VerticesIterable::Iterator VerticesIterable::begin() { + switch (type_) { + case Type::ALL: + return Iterator(all_vertices_.begin()); + case Type::BY_LABEL_IN_MEMORY: + return Iterator(in_memory_vertices_by_label_.begin()); + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + return Iterator(in_memory_vertices_by_label_property_.begin()); + } +} + +VerticesIterable::Iterator VerticesIterable::end() { + switch (type_) { + case Type::ALL: + return Iterator(all_vertices_.end()); + case Type::BY_LABEL_IN_MEMORY: + return Iterator(in_memory_vertices_by_label_.end()); + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + return Iterator(in_memory_vertices_by_label_property_.end()); + } +} + +VerticesIterable::Iterator::Iterator(AllVerticesIterable::Iterator it) : type_(Type::ALL) { + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&all_it_) AllVerticesIterable::Iterator(std::move(it)); +} + +VerticesIterable::Iterator::Iterator(InMemoryLabelIndex::Iterable::Iterator it) : type_(Type::BY_LABEL_IN_MEMORY) { + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&in_memory_by_label_it_) InMemoryLabelIndex::Iterable::Iterator(std::move(it)); +} + +VerticesIterable::Iterator::Iterator(InMemoryLabelPropertyIndex::Iterable::Iterator it) + : type_(Type::BY_LABEL_PROPERTY_IN_MEMORY) { + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&in_memory_by_label_property_it_) InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(it)); +} + +VerticesIterable::Iterator::Iterator(const VerticesIterable::Iterator &other) : type_(other.type_) { + switch (other.type_) { + case Type::ALL: + new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); + break; + case Type::BY_LABEL_IN_MEMORY: + new (&in_memory_by_label_it_) InMemoryLabelIndex::Iterable::Iterator(other.in_memory_by_label_it_); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_by_label_property_it_) + InMemoryLabelPropertyIndex::Iterable::Iterator(other.in_memory_by_label_property_it_); + break; + } +} + +// NOLINTNEXTLINE(cert-oop54-cpp) +VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const VerticesIterable::Iterator &other) { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); + break; + case Type::BY_LABEL_IN_MEMORY: + new (&in_memory_by_label_it_) InMemoryLabelIndex::Iterable::Iterator(other.in_memory_by_label_it_); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_by_label_property_it_) + InMemoryLabelPropertyIndex::Iterable::Iterator(other.in_memory_by_label_property_it_); + break; + } + return *this; +} + +VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::ALL: + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_)); + break; + case Type::BY_LABEL_IN_MEMORY: + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&in_memory_by_label_it_) InMemoryLabelIndex::Iterable::Iterator(std::move(other.in_memory_by_label_it_)); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_by_label_property_it_) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(other.in_memory_by_label_property_it_)); + break; + } +} + +VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterable::Iterator &&other) noexcept { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_)); + break; + case Type::BY_LABEL_IN_MEMORY: + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + new (&in_memory_by_label_it_) InMemoryLabelIndex::Iterable::Iterator(std::move(other.in_memory_by_label_it_)); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + new (&in_memory_by_label_property_it_) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + InMemoryLabelPropertyIndex::Iterable::Iterator(std::move(other.in_memory_by_label_property_it_)); + break; + } + return *this; +} + +VerticesIterable::Iterator::~Iterator() { Destroy(); } + +void VerticesIterable::Iterator::Destroy() noexcept { + switch (type_) { + case Type::ALL: + all_it_.AllVerticesIterable::Iterator::~Iterator(); + break; + case Type::BY_LABEL_IN_MEMORY: + in_memory_by_label_it_.InMemoryLabelIndex::Iterable::Iterator::~Iterator(); + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + in_memory_by_label_property_it_.InMemoryLabelPropertyIndex::Iterable::Iterator::~Iterator(); + break; + } +} + +VertexAccessor VerticesIterable::Iterator::operator*() const { + switch (type_) { + case Type::ALL: + return *all_it_; + case Type::BY_LABEL_IN_MEMORY: + return *in_memory_by_label_it_; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + return *in_memory_by_label_property_it_; + } +} + +VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() { + switch (type_) { + case Type::ALL: + ++all_it_; + break; + case Type::BY_LABEL_IN_MEMORY: + ++in_memory_by_label_it_; + break; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + ++in_memory_by_label_property_it_; + break; + } + return *this; +} + +bool VerticesIterable::Iterator::operator==(const Iterator &other) const { + switch (type_) { + case Type::ALL: + return all_it_ == other.all_it_; + case Type::BY_LABEL_IN_MEMORY: + return in_memory_by_label_it_ == other.in_memory_by_label_it_; + case Type::BY_LABEL_PROPERTY_IN_MEMORY: + return in_memory_by_label_property_it_ == other.in_memory_by_label_property_it_; + } +} + +} // namespace memgraph::storage diff --git a/src/storage/v2/vertices_iterable.hpp b/src/storage/v2/vertices_iterable.hpp new file mode 100644 index 000000000..7cd1fe208 --- /dev/null +++ b/src/storage/v2/vertices_iterable.hpp @@ -0,0 +1,78 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/all_vertices_iterable.hpp" +#include "storage/v2/inmemory/label_index.hpp" +#include "storage/v2/inmemory/label_property_index.hpp" + +namespace memgraph::storage { + +class VerticesIterable final { + enum class Type { ALL, BY_LABEL_IN_MEMORY, BY_LABEL_PROPERTY_IN_MEMORY }; + + Type type_; + union { + AllVerticesIterable all_vertices_; + InMemoryLabelIndex::Iterable in_memory_vertices_by_label_; + InMemoryLabelPropertyIndex::Iterable in_memory_vertices_by_label_property_; + }; + + public: + explicit VerticesIterable(AllVerticesIterable); + explicit VerticesIterable(InMemoryLabelIndex::Iterable); + explicit VerticesIterable(InMemoryLabelPropertyIndex::Iterable); + + VerticesIterable(const VerticesIterable &) = delete; + VerticesIterable &operator=(const VerticesIterable &) = delete; + + VerticesIterable(VerticesIterable &&) noexcept; + VerticesIterable &operator=(VerticesIterable &&) noexcept; + + ~VerticesIterable(); + + class Iterator final { + Type type_; + union { + AllVerticesIterable::Iterator all_it_; + InMemoryLabelIndex::Iterable::Iterator in_memory_by_label_it_; + InMemoryLabelPropertyIndex::Iterable::Iterator in_memory_by_label_property_it_; + }; + + void Destroy() noexcept; + + public: + explicit Iterator(AllVerticesIterable::Iterator); + explicit Iterator(InMemoryLabelIndex::Iterable::Iterator); + explicit Iterator(InMemoryLabelPropertyIndex::Iterable::Iterator); + + Iterator(const Iterator &); + Iterator &operator=(const Iterator &); + + Iterator(Iterator &&) noexcept; + Iterator &operator=(Iterator &&) noexcept; + + ~Iterator(); + + VertexAccessor operator*() const; + + Iterator &operator++(); + + bool operator==(const Iterator &other) const; + bool operator!=(const Iterator &other) const { return !(*this == other); } + }; + + Iterator begin(); + Iterator end(); +}; + +} // namespace memgraph::storage diff --git a/src/utils/disk_utils.hpp b/src/utils/disk_utils.hpp new file mode 100644 index 000000000..0372a38bb --- /dev/null +++ b/src/utils/disk_utils.hpp @@ -0,0 +1,28 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/delta.hpp" + +namespace memgraph::utils { + +inline std::optional<std::string> GetOldDiskKeyOrNull(storage::Delta *head) { + while (head->next != nullptr) { + head = head->next; + } + if (head->action == storage::Delta::Action::DELETE_DESERIALIZED_OBJECT) { + return head->old_disk_key; + } + return std::nullopt; +} + +} // namespace memgraph::utils diff --git a/src/utils/logging.hpp b/src/utils/logging.hpp index 87517b05a..0b8eaa639 100644 --- a/src/utils/logging.hpp +++ b/src/utils/logging.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -75,4 +75,15 @@ void Fatal(const char *msg, const Args &...msg_args) { #endif inline void RedirectToStderr() { spdlog::set_default_logger(spdlog::stderr_color_mt("stderr")); } + +// /// Use it for operations that must successfully finish. +inline void AssertRocksDBStatus(const auto &status) { MG_ASSERT(status.ok(), "rocksdb: {}", status.ToString()); } + +inline bool CheckRocksDBStatus(const auto &status) { + if (!status.ok()) [[unlikely]] { + spdlog::error("rocksdb: {}", status.ToString()); + } + return status.ok(); +} + } // namespace memgraph::logging diff --git a/src/utils/math.hpp b/src/utils/math.hpp index ab0e7a05d..250952a29 100644 --- a/src/utils/math.hpp +++ b/src/utils/math.hpp @@ -81,11 +81,9 @@ bool LessThanDecimal(T a, T b) { return (b - a) > std::numeric_limits<T>::epsilon(); } -/* - * return 0 if a == b - * return 1 if a > b - * return -1 if a < b - */ +/// @return 0 if a == b +/// @return 1 if a > b +/// @return -1 if a < b template <FloatingPoint T> int CompareDecimal(T a, T b) { if (ApproxEqualDecimal(a, b)) return 0; diff --git a/src/utils/memory.hpp b/src/utils/memory.hpp index 62b1f1d17..b764c83af 100644 --- a/src/utils/memory.hpp +++ b/src/utils/memory.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/utils/rocksdb_serialization.hpp b/src/utils/rocksdb_serialization.hpp new file mode 100644 index 000000000..5dbe3178b --- /dev/null +++ b/src/utils/rocksdb_serialization.hpp @@ -0,0 +1,291 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <cstdint> +#include <iomanip> +#include <iterator> +#include <numeric> +#include <string> + +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/id_types.hpp" +#include "storage/v2/property_store.hpp" +#include "storage/v2/vertex.hpp" +#include "storage/v2/vertex_accessor.hpp" +#include "utils/exceptions.hpp" +#include "utils/string.hpp" + +namespace memgraph::utils { + +static constexpr const char *outEdgeDirection = "0"; +static constexpr const char *inEdgeDirection = "1"; + +/// TODO: try to move this to hpp files so that we can follow jump on readings + +inline std::string SerializeIdType(const auto &id) { return std::to_string(id.AsUint()); } + +inline bool SerializedVertexHasLabels(const std::string &labels) { return !labels.empty(); } + +template <typename Collection> +inline std::vector<std::string> TransformIDsToString(const Collection &labels) { + std::vector<std::string> transformed_labels{}; + std::transform(labels.begin(), labels.end(), std::back_inserter(transformed_labels), + [](const auto &label) { return SerializeIdType(label); }); + return transformed_labels; +} + +inline std::vector<storage::LabelId> TransformFromStringLabels(const std::vector<std::string> &labels) { + std::vector<storage::LabelId> transformed_labels; + std::transform(labels.begin(), labels.end(), std::back_inserter(transformed_labels), + [](const auto &label) { return storage::LabelId::FromUint(std::stoull(label)); }); + return transformed_labels; +} + +inline std::string SerializeLabels(const std::vector<std::string> &labels) { + if (labels.empty()) { + return ""; + } + std::string result = labels[0]; + std::string ser_labels = + std::accumulate(std::next(labels.begin()), labels.end(), result, + [](const std::string &join, const auto &label_id) { return join + "," + label_id; }); + return ser_labels; +} + +inline std::string SerializeProperties(const storage::PropertyStore &properties) { return properties.StringBuffer(); } + +/// TODO: andi Probably it is better to add delimiter between label,property and the rest of labels +/// TODO: reuse PutIndexingLabelAndPropertiesFirst +inline std::string PutIndexingLabelAndPropertyFirst(const std::string &indexing_label, + const std::string &indexing_property, + const std::vector<std::string> &vertex_labels) { + std::string result = indexing_label + "," + indexing_property; + for (const auto &label : vertex_labels) { + if (label != indexing_label) { + result += "," + label; + } + } + return result; +} + +inline std::string PutIndexingLabelAndPropertiesFirst(const std::string &target_label, + const std::vector<std::string> &target_properties) { + std::string result = target_label; + for (const auto &target_property : target_properties) { + result += "," + target_property; + } + return result; +} + +inline std::string SerializeVertexAsValueForAuxiliaryStorages(storage::LabelId label_to_remove, + const std::vector<storage::LabelId> &vertex_labels, + const storage::PropertyStore &property_store) { + std::vector<storage::LabelId> labels_without_target; + std::copy_if(vertex_labels.begin(), vertex_labels.end(), std::back_inserter(labels_without_target), + [&label_to_remove](const auto &label) { return label_to_remove != label; }); + std::string result = SerializeLabels(TransformIDsToString(vertex_labels)) + "|"; + return result + SerializeProperties(property_store); +} + +inline std::string ExtractGidFromKey(const std::string &key) { + std::vector<std::string> key_vector = utils::Split(key, "|"); + return key_vector[1]; +} + +inline storage::PropertyStore DeserializePropertiesFromAuxiliaryStorages(const std::string &value) { + std::vector<std::string> value_vector = utils::Split(value, "|"); + std::string properties_str = value_vector[1]; + return storage::PropertyStore::CreateFromBuffer(properties_str); +} + +inline std::string SerializeVertex(const storage::Vertex &vertex) { + std::string result = utils::SerializeLabels(TransformIDsToString(vertex.labels)) + "|"; + result += utils::SerializeIdType(vertex.gid); + return result; +} + +inline std::vector<storage::LabelId> DeserializeLabelsFromMainDiskStorage(const std::string &key) { + std::vector<std::string> key_vector = utils::Split(key, "|"); + std::string labels_str = key_vector[0]; + if (SerializedVertexHasLabels(labels_str)) { + return TransformFromStringLabels(utils::Split(labels_str, ",")); + } + return {}; +} + +inline std::vector<std::string> ExtractLabelsFromMainDiskStorage(const std::string &key) { + std::vector<std::string> key_vector = utils::Split(key, "|"); + std::string labels_str = key_vector[0]; + return utils::Split(labels_str, ","); +} + +inline storage::PropertyStore DeserializePropertiesFromMainDiskStorage(const std::string_view value) { + return storage::PropertyStore::CreateFromBuffer(value); +} + +inline std::string ExtractGidFromMainDiskStorage(const std::string &key) { return ExtractGidFromKey(key); } + +inline std::string ExtractGidFromUniqueConstraintStorage(const std::string &key) { return ExtractGidFromKey(key); } + +/// Serialize vertex to string as a key in unique constraint index KV store. +/// target_label, target_property_1, target_property_2, ... GID | +/// commit_timestamp +inline std::string SerializeVertexAsKeyForUniqueConstraint(const storage::LabelId &constraint_label, + const std::set<storage::PropertyId> &constraint_properties, + const std::string &gid) { + auto key_for_indexing = PutIndexingLabelAndPropertiesFirst(SerializeIdType(constraint_label), + TransformIDsToString(constraint_properties)); + return key_for_indexing + "|" + gid; +} + +inline std::string SerializeVertexAsValueForUniqueConstraint(const storage::LabelId &constraint_label, + const std::vector<storage::LabelId> &vertex_labels, + const storage::PropertyStore &property_store) { + return SerializeVertexAsValueForAuxiliaryStorages(constraint_label, vertex_labels, property_store); +} + +inline storage::LabelId DeserializeConstraintLabelFromUniqueConstraintStorage(const std::string &key) { + std::vector<std::string> key_vector = utils::Split(key, "|"); + std::vector<std::string> constraint_key = utils::Split(key_vector[0], ","); + /// TODO: andi Change this to deserialization method directly into the LabelId class + return storage::LabelId::FromUint(std::stoull(constraint_key[0])); +} + +inline storage::PropertyStore DeserializePropertiesFromUniqueConstraintStorage(const std::string &value) { + return DeserializePropertiesFromAuxiliaryStorages(value); +} + +inline std::string SerializeVertexAsKeyForLabelIndex(const std::string &indexing_label, const std::string &gid) { + return indexing_label + "|" + gid; +} + +inline std::string SerializeVertexAsKeyForLabelIndex(storage::LabelId label, storage::Gid gid) { + return SerializeVertexAsKeyForLabelIndex(SerializeIdType(label), utils::SerializeIdType(gid)); +} + +inline std::string ExtractGidFromLabelIndexStorage(const std::string &key) { return ExtractGidFromKey(key); } + +inline std::string SerializeVertexAsValueForLabelIndex(storage::LabelId indexing_label, + const std::vector<storage::LabelId> &vertex_labels, + const storage::PropertyStore &property_store) { + return SerializeVertexAsValueForAuxiliaryStorages(indexing_label, vertex_labels, property_store); +} + +inline std::vector<storage::LabelId> DeserializeLabelsFromLabelIndexStorage(const std::string &value) { + const auto value_splitted = utils::Split(value, "|"); + return TransformFromStringLabels(utils::Split(value_splitted[0], ",")); +} + +inline storage::PropertyStore DeserializePropertiesFromLabelIndexStorage(const std::string &value) { + return DeserializePropertiesFromAuxiliaryStorages(value); +} + +inline std::string SerializeVertexAsKeyForLabelPropertyIndex(const std::string &indexing_label, + const std::string &indexing_property, + const std::string &gid) { + return indexing_label + "|" + indexing_property + "|" + gid; +} + +inline std::string SerializeVertexAsKeyForLabelPropertyIndex(storage::LabelId label, storage::PropertyId property, + storage::Gid gid) { + return SerializeVertexAsKeyForLabelPropertyIndex(SerializeIdType(label), SerializeIdType(property), + utils::SerializeIdType(gid)); +} + +inline std::string SerializeVertexAsValueForLabelPropertyIndex(storage::LabelId indexing_label, + const std::vector<storage::LabelId> &vertex_labels, + const storage::PropertyStore &property_store) { + return SerializeVertexAsValueForAuxiliaryStorages(indexing_label, vertex_labels, property_store); +} + +inline std::string ExtractGidFromLabelPropertyIndexStorage(const std::string &key) { + std::vector<std::string> key_vector = utils::Split(key, "|"); + return key_vector[2]; +} + +/// TODO: refactor into one method with label index storage +inline std::vector<storage::LabelId> DeserializeLabelsFromLabelPropertyIndexStorage(const std::string &value) { + const auto value_splitted = utils::Split(value, "|"); + return TransformFromStringLabels(utils::Split(value_splitted[0], ",")); +} + +inline storage::PropertyStore DeserializePropertiesFromLabelPropertyIndexStorage(const std::string &value) { + return DeserializePropertiesFromAuxiliaryStorages(value); +} + +/// Serialize edge as two KV entries +/// vertex_gid_1 | vertex_gid_2 | direction | edge_type | GID | commit_timestamp +inline std::string SerializeEdge(storage::EdgeAccessor *edge_acc) { + // Serialized objects + auto from_gid = utils::SerializeIdType(edge_acc->FromVertex().Gid()); + auto to_gid = utils::SerializeIdType(edge_acc->ToVertex().Gid()); + auto edge_type = utils::SerializeIdType(edge_acc->EdgeType()); + auto edge_gid = utils::SerializeIdType(edge_acc->Gid()); + // source->destination key + std::string src_dest_key = from_gid + "|"; + src_dest_key += to_gid + "|"; + src_dest_key += outEdgeDirection; + src_dest_key += "|" + edge_type + "|"; + src_dest_key += edge_gid; + return src_dest_key; +} + +/// Serialize edge as two KV entries +/// vertex_gid_1 | vertex_gid_2 | direction | edge_type | GID | commit_timestamp +/// @tparam src_vertex_gid, dest_vertex_gid: Gid of the source and destination vertices +/// @tparam edge: Edge to be serialized +/// @tparam edge_type_id: EdgeTypeId of the edge +inline std::string SerializeEdge(storage::Gid src_vertex_gid, storage::Gid dest_vertex_gid, + storage::EdgeTypeId edge_type_id, const storage::EdgeRef edge_ref, + bool properties_on_edges) { + // Serialized objects + auto from_gid = utils::SerializeIdType(src_vertex_gid); + auto to_gid = utils::SerializeIdType(dest_vertex_gid); + auto edge_type = utils::SerializeIdType(edge_type_id); + std::string edge_gid; + + if (properties_on_edges) { + edge_gid = utils::SerializeIdType(edge_ref.ptr->gid); + } else { + edge_gid = utils::SerializeIdType(edge_ref.gid); + } + + // source->destination key + std::string src_dest_key = from_gid + "|"; + src_dest_key += to_gid + "|"; + src_dest_key += outEdgeDirection; + src_dest_key += "|" + edge_type + "|"; + src_dest_key += edge_gid; + return src_dest_key; +} + +/// TODO: (andi): This can potentially be a problem on big-endian machines. +inline void PutFixed64(std::string *dst, uint64_t value) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + dst->append(const_cast<const char *>(reinterpret_cast<char *>(&value)), sizeof(value)); +} + +inline uint64_t DecodeFixed64(const char *ptr) { + // Load the raw bytes + uint64_t result = 0; + memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load + return result; +} + +inline std::string StringTimestamp(uint64_t ts) { + std::string ret; + PutFixed64(&ret, ts); + return ret; +} + +} // namespace memgraph::utils diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index 69326ab5e..2a7090678 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -21,13 +21,17 @@ #include <random> #include <utility> +#include "spdlog/spdlog.h" #include "utils/bound.hpp" #include "utils/linux.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" +#include "utils/memory_tracker.hpp" #include "utils/on_scope_exit.hpp" +#include "utils/readable_size.hpp" #include "utils/spin_lock.hpp" #include "utils/stack.hpp" +#include "utils/stat.hpp" // This code heavily depends on atomic operations. For a more detailed // description of how exactly atomic operations work, see: @@ -345,9 +349,6 @@ class SkipListGc final { MemoryResource *GetMemoryResource() const { return memory_; } void Clear() { -#ifndef NDEBUG - MG_ASSERT(alive_accessors_ == 0, "The SkipList can't be cleared while there are existing accessors!"); -#endif // Delete all allocated blocks. Block *head = head_.load(std::memory_order_acquire); while (head != nullptr) { @@ -890,7 +891,7 @@ class SkipList final { MemoryResource *GetMemoryResource() const { return gc_.GetMemoryResource(); } /// This function removes all elements from the list. - /// NOTE: The function *isn't* thread-safe. It must be called while there are + /// NOTE: The function *isn't* thread-safe. It must be called only if there are /// no more active accessors using the list. void clear() { TNode *curr = head_->nexts[0].load(std::memory_order_acquire); diff --git a/tests/benchmark/expansion.cpp b/tests/benchmark/expansion.cpp index 5ff412eea..5b7988f3a 100644 --- a/tests/benchmark/expansion.cpp +++ b/tests/benchmark/expansion.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,46 +15,44 @@ #include "query/config.hpp" #include "query/interpreter.hpp" #include "query/typed_value.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" -#include "storage/v2/storage.hpp" class ExpansionBenchFixture : public benchmark::Fixture { protected: - std::optional<memgraph::storage::Storage> db; std::optional<memgraph::query::InterpreterContext> interpreter_context; std::optional<memgraph::query::Interpreter> interpreter; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "expansion-benchmark"}; void SetUp(const benchmark::State &state) override { - db.emplace(); + interpreter_context.emplace(memgraph::storage::Config{}, memgraph::query::InterpreterConfig{}, data_directory); + auto *db = interpreter_context->db.get(); auto label = db->NameToLabel("Starting"); { auto dba = db->Access(); - for (int i = 0; i < state.range(0); i++) dba.CreateVertex(); + for (int i = 0; i < state.range(0); i++) dba->CreateVertex(); // the fixed part is one vertex expanding to 1000 others - auto start = dba.CreateVertex(); + auto start = dba->CreateVertex(); MG_ASSERT(start.AddLabel(label).HasValue()); - auto edge_type = dba.NameToEdgeType("edge_type"); + auto edge_type = dba->NameToEdgeType("edge_type"); for (int i = 0; i < 1000; i++) { - auto dest = dba.CreateVertex(); - MG_ASSERT(dba.CreateEdge(&start, &dest, edge_type).HasValue()); + auto dest = dba->CreateVertex(); + MG_ASSERT(dba->CreateEdge(&start, &dest, edge_type).HasValue()); } - MG_ASSERT(!dba.Commit().HasError()); + MG_ASSERT(!dba->Commit().HasError()); } MG_ASSERT(!db->CreateIndex(label).HasError()); - interpreter_context.emplace(&*db, memgraph::query::InterpreterConfig{}, data_directory); interpreter.emplace(&*interpreter_context); } void TearDown(const benchmark::State &) override { interpreter = std::nullopt; interpreter_context = std::nullopt; - db = std::nullopt; std::filesystem::remove_all(data_directory); } }; @@ -63,7 +61,7 @@ BENCHMARK_DEFINE_F(ExpansionBenchFixture, Match)(benchmark::State &state) { auto query = "MATCH (s:Starting) return s"; while (state.KeepRunning()) { - ResultStreamFaker results(&*db); + ResultStreamFaker results(interpreter_context->db.get()); interpreter->Prepare(query, {}, nullptr); interpreter->PullAll(&results); } @@ -78,7 +76,7 @@ BENCHMARK_DEFINE_F(ExpansionBenchFixture, Expand)(benchmark::State &state) { auto query = "MATCH (s:Starting) WITH s MATCH (s)--(d) RETURN count(d)"; while (state.KeepRunning()) { - ResultStreamFaker results(&*db); + ResultStreamFaker results(interpreter_context->db.get()); interpreter->Prepare(query, {}, nullptr); interpreter->PullAll(&results); } diff --git a/tests/benchmark/query/eval.cpp b/tests/benchmark/query/eval.cpp index 72e185793..5e5acea7f 100644 --- a/tests/benchmark/query/eval.cpp +++ b/tests/benchmark/query/eval.cpp @@ -14,6 +14,7 @@ #include "query/db_accessor.hpp" #include "query/interpret/eval.hpp" #include "query/interpreter.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can @@ -38,9 +39,9 @@ static void MapLiteral(benchmark::State &state) { memgraph::query::SymbolTable symbol_table; TMemory memory; memgraph::query::Frame frame(symbol_table.max_position(), memory.get()); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *> elements; for (int64_t i = 0; i < state.range(0); ++i) { elements.emplace(ast.GetPropertyIx("prop" + std::to_string(i)), ast.Create<memgraph::query::PrimitiveLiteral>(i)); @@ -69,9 +70,9 @@ static void AdditionOperator(benchmark::State &state) { memgraph::query::SymbolTable symbol_table; TMemory memory; memgraph::query::Frame frame(symbol_table.max_position(), memory.get()); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::query::Expression *expr = ast.Create<memgraph::query::PrimitiveLiteral>(0); for (int64_t i = 0; i < state.range(0); ++i) { expr = ast.Create<memgraph::query::AdditionOperator>(expr, ast.Create<memgraph::query::PrimitiveLiteral>(i)); diff --git a/tests/benchmark/query/execution.cpp b/tests/benchmark/query/execution.cpp index 69aedaff9..1d1f26d3f 100644 --- a/tests/benchmark/query/execution.cpp +++ b/tests/benchmark/query/execution.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -30,7 +30,7 @@ #include "query/frontend/semantic/required_privileges.hpp" #include "query/frontend/semantic/symbol_generator.hpp" #include "query/interpreter.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can // use BENCHMARK_TEMPLATE @@ -62,8 +62,8 @@ class PoolResource final { static void AddVertices(memgraph::storage::Storage *db, int vertex_count) { auto dba = db->Access(); - for (int i = 0; i < vertex_count; i++) dba.CreateVertex(); - MG_ASSERT(!dba.Commit().HasError()); + for (int i = 0; i < vertex_count; i++) dba->CreateVertex(); + MG_ASSERT(!dba->Commit().HasError()); } static const char *kStartLabel = "start"; @@ -71,17 +71,17 @@ static const char *kStartLabel = "start"; static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int depth) { { auto dba = db->Access(); - auto center_vertex = dba.CreateVertex(); - MG_ASSERT(center_vertex.AddLabel(dba.NameToLabel(kStartLabel)).HasValue()); + auto center_vertex = dba->CreateVertex(); + MG_ASSERT(center_vertex.AddLabel(dba->NameToLabel(kStartLabel)).HasValue()); for (int i = 0; i < spoke_count; ++i) { auto prev_vertex = center_vertex; for (int j = 0; j < depth; ++j) { - auto dest = dba.CreateVertex(); - MG_ASSERT(dba.CreateEdge(&prev_vertex, &dest, dba.NameToEdgeType("Type")).HasValue()); + auto dest = dba->CreateVertex(); + MG_ASSERT(dba->CreateEdge(&prev_vertex, &dest, dba->NameToEdgeType("Type")).HasValue()); prev_vertex = dest; } } - MG_ASSERT(!dba.Commit().HasError()); + MG_ASSERT(!dba->Commit().HasError()); } MG_ASSERT(!db->CreateIndex(db->NameToLabel(kStartLabel)).HasError()); } @@ -91,19 +91,19 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) { auto dba = db->Access(); std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(vertex_count); - auto root = dba.CreateVertex(); - MG_ASSERT(root.AddLabel(dba.NameToLabel(kStartLabel)).HasValue()); + auto root = dba->CreateVertex(); + MG_ASSERT(root.AddLabel(dba->NameToLabel(kStartLabel)).HasValue()); vertices.push_back(root); // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp) std::mt19937_64 rg(42); for (int i = 1; i < vertex_count; ++i) { - auto v = dba.CreateVertex(); + auto v = dba->CreateVertex(); std::uniform_int_distribution<> dis(0U, vertices.size() - 1U); auto &parent = vertices.at(dis(rg)); - MG_ASSERT(dba.CreateEdge(&parent, &v, dba.NameToEdgeType("Type")).HasValue()); + MG_ASSERT(dba->CreateEdge(&parent, &v, dba->NameToEdgeType("Type")).HasValue()); vertices.push_back(v); } - MG_ASSERT(!dba.Commit().HasError()); + MG_ASSERT(!dba->Commit().HasError()); } MG_ASSERT(!db->CreateIndex(db->NameToLabel(kStartLabel)).HasError()); } @@ -124,16 +124,16 @@ template <class TMemory> static void Distinct(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddVertices(&db, state.range(0)); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddVertices(db.get(), state.range(0)); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto query_string = "MATCH (s) RETURN DISTINCT s"; auto *cypher_query = ParseCypherQuery(query_string, &ast); auto symbol_table = memgraph::query::MakeSymbolTable(cypher_query); auto context = memgraph::query::plan::MakePlanningContext(&ast, &symbol_table, cypher_query, &dba); auto plan_and_cost = memgraph::query::plan::MakeLogicalPlan(&context, parameters, false); - ResultStreamFaker results(&db); + ResultStreamFaker results(db.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -174,12 +174,12 @@ template <class TMemory> static void ExpandVariable(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddStarGraph(&db, state.range(0), state.range(1)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddStarGraph(db.get(), state.range(0), state.range(1)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::DEPTH_FIRST, &symbol_table); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -213,12 +213,12 @@ template <class TMemory> static void ExpandBfs(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddTree(&db, state.range(0)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddTree(db.get(), state.range(0)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -246,14 +246,14 @@ template <class TMemory> static void ExpandShortest(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddTree(&db, state.range(0)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddTree(db.get(), state.range(0)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table); expand_variable.common_.existing_node = true; auto dest_symbol = expand_variable.common_.node_symbol; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -284,8 +284,8 @@ template <class TMemory> static void ExpandWeightedShortest(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddTree(&db, state.range(0)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddTree(db.get(), state.range(0)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, &symbol_table); expand_variable.common_.existing_node = true; @@ -293,8 +293,8 @@ static void ExpandWeightedShortest(benchmark::State &state) { symbol_table.CreateSymbol("edge", false), symbol_table.CreateSymbol("vertex", false), ast.Create<memgraph::query::PrimitiveLiteral>(1)}; auto dest_symbol = expand_variable.common_.node_symbol; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -327,8 +327,8 @@ template <class TMemory> static void Accumulate(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddVertices(&db, state.range(1)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddVertices(db.get(), state.range(1)); memgraph::query::SymbolTable symbol_table; auto scan_all = std::make_shared<memgraph::query::plan::ScanAll>(nullptr, symbol_table.CreateSymbol("v", false)); std::vector<memgraph::query::Symbol> symbols; @@ -338,8 +338,8 @@ static void Accumulate(benchmark::State &state) { } memgraph::query::plan::Accumulate accumulate(scan_all, symbols, /* advance_command= */ false); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -368,8 +368,8 @@ template <class TMemory> static void Aggregate(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddVertices(&db, state.range(1)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddVertices(db.get(), state.range(1)); memgraph::query::SymbolTable symbol_table; auto scan_all = std::make_shared<memgraph::query::plan::ScanAll>(nullptr, symbol_table.CreateSymbol("v", false)); std::vector<memgraph::query::Symbol> symbols; @@ -387,8 +387,8 @@ static void Aggregate(benchmark::State &state) { symbol_table.CreateSymbol("out" + std::to_string(i), false)}); } memgraph::query::plan::Aggregate aggregate(scan_all, aggregations, group_by, symbols); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -421,8 +421,8 @@ template <class TMemory> static void OrderBy(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddVertices(&db, state.range(1)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddVertices(db.get(), state.range(1)); memgraph::query::SymbolTable symbol_table; auto scan_all = std::make_shared<memgraph::query::plan::ScanAll>(nullptr, symbol_table.CreateSymbol("v", false)); std::vector<memgraph::query::Symbol> symbols; @@ -437,8 +437,8 @@ static void OrderBy(benchmark::State &state) { sort_items.push_back({memgraph::query::Ordering::ASC, ast.Create<memgraph::query::PrimitiveLiteral>(rand_value)}); } memgraph::query::plan::OrderBy order_by(scan_all, sort_items, symbols); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -467,16 +467,16 @@ template <class TMemory> static void Unwind(benchmark::State &state) { memgraph::query::AstStorage ast; memgraph::query::Parameters parameters; - memgraph::storage::Storage db; - AddVertices(&db, state.range(0)); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + AddVertices(db.get(), state.range(0)); memgraph::query::SymbolTable symbol_table; auto scan_all = std::make_shared<memgraph::query::plan::ScanAll>(nullptr, symbol_table.CreateSymbol("v", false)); auto list_sym = symbol_table.CreateSymbol("list", false); auto *list_expr = ast.Create<memgraph::query::Identifier>("list")->MapTo(list_sym); auto out_sym = symbol_table.CreateSymbol("out", false); memgraph::query::plan::Unwind unwind(scan_all, list_expr, out_sym); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; @@ -503,7 +503,7 @@ template <class TMemory> // NOLINTNEXTLINE(google-runtime-references) static void Foreach(benchmark::State &state) { memgraph::query::AstStorage ast; - memgraph::storage::Storage db; + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); memgraph::query::SymbolTable symbol_table; auto list_sym = symbol_table.CreateSymbol("list", false); auto *list_expr = ast.Create<memgraph::query::Identifier>("list")->MapTo(list_sym); @@ -512,8 +512,8 @@ static void Foreach(benchmark::State &state) { std::make_shared<memgraph::query::plan::CreateNode>(nullptr, memgraph::query::plan::NodeCreationInfo{}); auto foreach = std::make_shared<memgraph::query::plan::Foreach>(nullptr, std::move(create_node), list_expr, out_sym); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; while (state.KeepRunning()) { diff --git a/tests/benchmark/query/planner.cpp b/tests/benchmark/query/planner.cpp index 4fe4ee28c..2beb4fc41 100644 --- a/tests/benchmark/query/planner.cpp +++ b/tests/benchmark/query/planner.cpp @@ -17,7 +17,7 @@ #include "query/plan/cost_estimator.hpp" #include "query/plan/planner.hpp" #include "query/plan/vertex_count_cache.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" // Add chained MATCH (node1) -- (node2), MATCH (node2) -- (node3) ... clauses. static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph::query::AstStorage &storage) { @@ -43,9 +43,9 @@ static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph } static void BM_PlanChainedMatches(benchmark::State &state) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); while (state.KeepRunning()) { state.PauseTiming(); memgraph::query::AstStorage storage; @@ -98,24 +98,24 @@ static auto CreateIndexedVertices(int index_count, int vertex_count, memgraph::s auto dba = db->Access(); for (int vi = 0; vi < vertex_count; ++vi) { for (int index = 0; index < index_count; ++index) { - auto vertex = dba.CreateVertex(); + auto vertex = dba->CreateVertex(); MG_ASSERT(vertex.AddLabel(label).HasValue()); MG_ASSERT(vertex.SetProperty(prop, memgraph::storage::PropertyValue(index)).HasValue()); } } - MG_ASSERT(!dba.Commit().HasError()); + MG_ASSERT(!dba->Commit().HasError()); return std::make_pair("label", "prop"); } static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { - memgraph::storage::Storage db; + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); std::string label; std::string prop; int index_count = state.range(0); int vertex_count = state.range(1); - std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, &db); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::query::Parameters parameters; while (state.KeepRunning()) { state.PauseTiming(); @@ -137,14 +137,14 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { } static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State &state) { - memgraph::storage::Storage db; + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); std::string label; std::string prop; int index_count = state.range(0); int vertex_count = state.range(1); - std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, &db); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex_counts = memgraph::query::plan::MakeVertexCountCache(&dba); memgraph::query::Parameters parameters; while (state.KeepRunning()) { diff --git a/tests/benchmark/storage_v2_gc.cpp b/tests/benchmark/storage_v2_gc.cpp index a8f023ced..3941fcbe1 100644 --- a/tests/benchmark/storage_v2_gc.cpp +++ b/tests/benchmark/storage_v2_gc.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,6 +13,7 @@ #include <gflags/gflags.h> +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "utils/timer.hpp" @@ -43,12 +44,12 @@ void UpdateLabelFunc(int thread_id, memgraph::storage::Storage *storage, for (int iter = 0; iter < num_iterations; ++iter) { auto acc = storage->Access(); memgraph::storage::Gid gid = vertices.at(vertex_dist(gen)); - std::optional<memgraph::storage::VertexAccessor> vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); MG_ASSERT(vertex.has_value(), "Vertex with GID {} doesn't exist", gid.AsUint()); if (vertex->AddLabel(memgraph::storage::LabelId::FromUint(label_dist(gen))).HasValue()) { - MG_ASSERT(!acc.Commit().HasError()); + MG_ASSERT(!acc->Commit().HasError()); } else { - acc.Abort(); + acc->Abort(); } } } @@ -57,21 +58,21 @@ int main(int argc, char *argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); for (const auto &config : TestConfigurations) { - memgraph::storage::Storage storage(config.second); + std::unique_ptr<memgraph::storage::Storage> storage(new memgraph::storage::InMemoryStorage(config.second)); std::vector<memgraph::storage::Gid> vertices; { - auto acc = storage.Access(); + auto acc = storage->Access(); for (int i = 0; i < FLAGS_num_vertices; ++i) { - vertices.push_back(acc.CreateVertex().Gid()); + vertices.push_back(acc->CreateVertex().Gid()); } - MG_ASSERT(!acc.Commit().HasError()); + MG_ASSERT(!acc->Commit().HasError()); } memgraph::utils::Timer timer; std::vector<std::thread> threads; threads.reserve(FLAGS_num_threads); for (int i = 0; i < FLAGS_num_threads; ++i) { - threads.emplace_back(UpdateLabelFunc, i, &storage, vertices, FLAGS_num_iterations); + threads.emplace_back(UpdateLabelFunc, i, storage.get(), vertices, FLAGS_num_iterations); } for (int i = 0; i < FLAGS_num_threads; ++i) { diff --git a/tests/concurrent/storage_indices.cpp b/tests/concurrent/storage_indices.cpp index 8c50ef18b..4757be189 100644 --- a/tests/concurrent/storage_indices.cpp +++ b/tests/concurrent/storage_indices.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,7 +15,7 @@ #include <fmt/format.h> #include <gtest/gtest.h> -#include "storage/v2/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage_error.hpp" #include "utils/thread.hpp" @@ -27,10 +27,10 @@ const uint64_t kVerifierBatchSize = 10; const uint64_t kMutatorBatchSize = 1000; TEST(Storage, LabelIndex) { - auto store = memgraph::storage::Storage(); + std::unique_ptr<memgraph::storage::Storage> store{new memgraph::storage::InMemoryStorage()}; - auto label = store.NameToLabel("label"); - ASSERT_FALSE(store.CreateIndex(label).HasError()); + auto label = store->NameToLabel("label"); + ASSERT_FALSE(store->CreateIndex(label).HasError()); std::vector<std::thread> verifiers; verifiers.reserve(kNumVerifiers); @@ -41,17 +41,17 @@ TEST(Storage, LabelIndex) { gids.reserve(kNumIterations * kVerifierBatchSize); for (uint64_t i = 0; i < kNumIterations; ++i) { for (uint64_t j = 0; j < kVerifierBatchSize; ++j) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids.emplace(vertex.Gid(), false); auto ret = vertex.AddLabel(label); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertices = acc.Vertices(label, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertices = acc->Vertices(label, memgraph::storage::View::OLD); for (auto vertex : vertices) { auto it = gids.find(vertex.Gid()); if (it != gids.end()) { @@ -78,20 +78,20 @@ TEST(Storage, LabelIndex) { gids.resize(kMutatorBatchSize); while (mutators_run.load(std::memory_order_acquire)) { for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids[i] = vertex.Gid(); auto ret = vertex.AddLabel(label); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(acc.DeleteVertex(&*vertex).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } } }); @@ -108,11 +108,11 @@ TEST(Storage, LabelIndex) { } TEST(Storage, LabelPropertyIndex) { - auto store = memgraph::storage::Storage(); + std::unique_ptr<memgraph::storage::Storage> store{new memgraph::storage::InMemoryStorage()}; - auto label = store.NameToLabel("label"); - auto prop = store.NameToProperty("prop"); - ASSERT_FALSE(store.CreateIndex(label, prop).HasError()); + auto label = store->NameToLabel("label"); + auto prop = store->NameToProperty("prop"); + ASSERT_FALSE(store->CreateIndex(label, prop).HasError()); std::vector<std::thread> verifiers; verifiers.reserve(kNumVerifiers); @@ -123,8 +123,8 @@ TEST(Storage, LabelPropertyIndex) { gids.reserve(kNumIterations * kVerifierBatchSize); for (uint64_t i = 0; i < kNumIterations; ++i) { for (uint64_t j = 0; j < kVerifierBatchSize; ++j) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids.emplace(vertex.Gid(), false); { auto ret = vertex.AddLabel(label); @@ -136,11 +136,11 @@ TEST(Storage, LabelPropertyIndex) { ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertices = acc.Vertices(label, prop, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertices = acc->Vertices(label, prop, memgraph::storage::View::OLD); for (auto vertex : vertices) { auto it = gids.find(vertex.Gid()); if (it != gids.end()) { @@ -167,8 +167,8 @@ TEST(Storage, LabelPropertyIndex) { gids.resize(kMutatorBatchSize); while (mutators_run.load(std::memory_order_acquire)) { for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids[i] = vertex.Gid(); { auto ret = vertex.AddLabel(label); @@ -180,14 +180,14 @@ TEST(Storage, LabelPropertyIndex) { ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(acc.DeleteVertex(&*vertex).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } } }); diff --git a/tests/concurrent/storage_unique_constraints.cpp b/tests/concurrent/storage_unique_constraints.cpp index 63ad00abb..f2f73930e 100644 --- a/tests/concurrent/storage_unique_constraints.cpp +++ b/tests/concurrent/storage_unique_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,8 +13,8 @@ #include <gtest/gtest.h> -#include "storage/v2/constraints.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/inmemory/storage.hpp" const int kNumThreads = 8; @@ -27,23 +27,23 @@ using memgraph::storage::PropertyValue; class StorageUniqueConstraints : public ::testing::Test { protected: StorageUniqueConstraints() - : label(storage.NameToLabel("label")), - prop1(storage.NameToProperty("prop1")), - prop2(storage.NameToProperty("prop2")), - prop3(storage.NameToProperty("prop3")) {} + : label(storage->NameToLabel("label")), + prop1(storage->NameToProperty("prop1")), + prop2(storage->NameToProperty("prop2")), + prop3(storage->NameToProperty("prop3")) {} void SetUp() override { // Create initial vertices. - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.CreateVertex(); + auto vertex = acc->CreateVertex(); gids[i] = vertex.Gid(); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } - memgraph::storage::Storage storage; + std::unique_ptr<memgraph::storage::Storage> storage{new memgraph::storage::InMemoryStorage()}; LabelId label; PropertyId prop1; PropertyId prop2; @@ -56,7 +56,7 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g bool *commit_status) { ASSERT_EQ(properties.size(), values.size()); auto acc = storage->Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); int value = 0; for (int iter = 0; iter < 40000; ++iter) { @@ -67,37 +67,37 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g for (size_t i = 0; i < properties.size(); ++i) { ASSERT_OK(vertex->SetProperty(properties[i], values[i])); } - *commit_status = !acc.Commit().HasError(); + *commit_status = !acc->Commit().HasError(); } void AddLabel(memgraph::storage::Storage *storage, memgraph::storage::Gid gid, LabelId label, bool *commit_status) { auto acc = storage->Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); for (int iter = 0; iter < 40000; ++iter) { ASSERT_OK(vertex->AddLabel(label)); ASSERT_OK(vertex->RemoveLabel(label)); } ASSERT_OK(vertex->AddLabel(label)); - *commit_status = !acc.Commit().HasError(); + *commit_status = !acc->Commit().HasError(); } TEST_F(StorageUniqueConstraints, ChangeProperties) { { - auto res = storage.CreateUniqueConstraint(label, {prop1, prop2, prop3}); + auto res = storage->CreateUniqueConstraint(label, {prop1, prop2, prop3}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_OK(vertex->AddLabel(label)); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } std::vector<PropertyId> properties{prop1, prop2, prop3}; @@ -111,7 +111,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { std::vector<std::thread> threads; threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { - threads.emplace_back(SetProperties, &storage, gids[i], properties, values, &status[i]); + threads.emplace_back(SetProperties, storage.get(), gids[i], properties, values, &status[i]); } int count_ok = 0; for (int i = 0; i < kNumThreads; ++i) { @@ -131,7 +131,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { std::vector<std::thread> threads; threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { - threads.emplace_back(SetProperties, &storage, gids[i], properties, values, &status[i]); + threads.emplace_back(SetProperties, storage.get(), gids[i], properties, values, &status[i]); } int count_ok = 0; for (int i = 0; i < kNumThreads; ++i) { @@ -152,7 +152,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { std::vector<PropertyValue> values{PropertyValue(value++), PropertyValue(value++), PropertyValue(value++)}; - threads.emplace_back(SetProperties, &storage, gids[i], properties, values, &status[i]); + threads.emplace_back(SetProperties, storage.get(), gids[i], properties, values, &status[i]); } int count_ok = 0; for (int i = 0; i < kNumThreads; ++i) { @@ -166,7 +166,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { TEST_F(StorageUniqueConstraints, ChangeLabels) { { - auto res = storage.CreateUniqueConstraint(label, {prop1, prop2, prop3}); + auto res = storage->CreateUniqueConstraint(label, {prop1, prop2, prop3}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } @@ -177,29 +177,29 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { // succeed, as the others should result with constraint violation. { - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_OK(vertex->SetProperty(prop1, PropertyValue(1))); ASSERT_OK(vertex->SetProperty(prop2, PropertyValue(2))); ASSERT_OK(vertex->SetProperty(prop3, PropertyValue(3))); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } for (int iter = 0; iter < 20; ++iter) { // Clear labels. { - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_OK(vertex->RemoveLabel(label)); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } bool status[kNumThreads]; @@ -207,7 +207,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { threads.reserve(kNumThreads); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - threads.emplace_back(AddLabel, &storage, gids[i], label, &status[i]); + threads.emplace_back(AddLabel, storage.get(), gids[i], label, &status[i]); } int count_ok = 0; // NOLINTNEXTLINE(modernize-loop-convert) @@ -223,36 +223,36 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { // should succeed. { - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_OK(vertex->SetProperty(prop1, PropertyValue(3 * i))); ASSERT_OK(vertex->SetProperty(prop2, PropertyValue(3 * i + 1))); ASSERT_OK(vertex->SetProperty(prop3, PropertyValue(3 * i + 2))); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } for (int iter = 0; iter < 20; ++iter) { // Clear labels. { - auto acc = storage.Access(); + auto acc = storage->Access(); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_OK(vertex->RemoveLabel(label)); } - ASSERT_OK(acc.Commit()); + ASSERT_OK(acc->Commit()); } bool status[kNumThreads]; std::vector<std::thread> threads; threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; ++i) { - threads.emplace_back(AddLabel, &storage, gids[i], label, &status[i]); + threads.emplace_back(AddLabel, storage.get(), gids[i], label, &status[i]); } int count_ok = 0; for (int i = 0; i < kNumThreads; ++i) { diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index bb0341123..6f729c968 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -55,6 +55,7 @@ add_subdirectory(python_query_modules_reloading) add_subdirectory(analyze_graph) add_subdirectory(transaction_queue) add_subdirectory(mock_api) +add_subdirectory(disk_storage) add_subdirectory(load_csv) add_subdirectory(init_file_flags) add_subdirectory(analytical_mode) diff --git a/tests/e2e/disk_storage/CMakeLists.txt b/tests/e2e/disk_storage/CMakeLists.txt new file mode 100644 index 000000000..680f980f6 --- /dev/null +++ b/tests/e2e/disk_storage/CMakeLists.txt @@ -0,0 +1,14 @@ +function(copy_disk_storage_e2e_python_files FILE_NAME) + copy_e2e_python_files(disk_storage ${FILE_NAME}) +endfunction() + +copy_disk_storage_e2e_python_files(common.py) +copy_disk_storage_e2e_python_files(data_import.py) +copy_disk_storage_e2e_python_files(update_storage_mode_db_not_empty.py) +copy_disk_storage_e2e_python_files(update_storage_mode_disk_to_memory.py) +copy_disk_storage_e2e_python_files(update_storage_mode_memory_to_disk.py) +copy_disk_storage_e2e_python_files(free_memory_disabled.py) +copy_disk_storage_e2e_python_files(replication_disabled.py) +copy_disk_storage_e2e_python_files(snapshot_disabled.py) +copy_disk_storage_e2e_python_files(lock_data_dir_disabled.py) +copy_disk_storage_e2e_python_files(create_edge_from_indices.py) diff --git a/tests/e2e/disk_storage/common.py b/tests/e2e/disk_storage/common.py new file mode 100644 index 000000000..fb60257df --- /dev/null +++ b/tests/e2e/disk_storage/common.py @@ -0,0 +1,27 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import typing + +import mgclient +import pytest + + +def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]: + cursor.execute(query, params) + return cursor.fetchall() + + +@pytest.fixture +def connect(**kwargs) -> mgclient.Connection: + connection = mgclient.connect(host="localhost", port=7687, **kwargs) + connection.autocommit = True + return connection diff --git a/tests/e2e/disk_storage/create_edge_from_indices.py b/tests/e2e/disk_storage/create_edge_from_indices.py new file mode 100644 index 000000000..a1bb0a35c --- /dev/null +++ b/tests/e2e/disk_storage/create_edge_from_indices.py @@ -0,0 +1,34 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys +import typing + +import pytest +from common import connect, execute_and_fetch_all + + +def test_creating_edges_by_loading_vertices_from_index(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + execute_and_fetch_all(cursor, "CREATE (:User {id: 1, completion_percentage: 14, gender: 'man', age: 26})") + execute_and_fetch_all(cursor, "CREATE (:User {id: 2, completion_percentage: 15, gender: 'man', age: 30})") + execute_and_fetch_all(cursor, "CREATE INDEX ON :User(id)") + execute_and_fetch_all(cursor, "MATCH (n:User {id: 1}), (m:User {id: 2}) CREATE (n)-[e: Friend]->(m)") + # n and m will be in the index cache + # edge will be created and put it into the main memory edge cache + # we just iterate over vertices from main memory cache. -> this cache is empty + # iterating over edges is done by using out_edges of each vertex + assert len(execute_and_fetch_all(cursor, "MATCH (n:User {id: 1})-[e: Friend]->(m:User {id: 2}) RETURN e")) == 1 + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/data_import.py b/tests/e2e/disk_storage/data_import.py new file mode 100644 index 000000000..ddf5fc5d2 --- /dev/null +++ b/tests/e2e/disk_storage/data_import.py @@ -0,0 +1,45 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + +num_entries = 100000 + + +def test_disk_import_fail(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "FOREACH (i IN range(1, {num_entries}) | CREATE (n:DiskLabel {{id: i}}));") + assert False + except: + assert True + + +def test_batched_disk_import_passes(connect): + step = int(num_entries / 5) + for i in range(1, num_entries, step): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + query = "FOREACH (i IN range({i}, {i} + {step}) | CREATE (n:DiskLabel {{id: {i}}}));".format(i=i, step=step) + try: + execute_and_fetch_all(cursor, query) + except: + assert False + cursor.close() + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/free_memory_disabled.py b/tests/e2e/disk_storage/free_memory_disabled.py new file mode 100644 index 000000000..4023f4214 --- /dev/null +++ b/tests/e2e/disk_storage/free_memory_disabled.py @@ -0,0 +1,29 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_free_memory_is_disabled(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "FREE MEMORY") + assert False + except: + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/lock_data_dir_disabled.py b/tests/e2e/disk_storage/lock_data_dir_disabled.py new file mode 100644 index 000000000..a20848c4f --- /dev/null +++ b/tests/e2e/disk_storage/lock_data_dir_disabled.py @@ -0,0 +1,29 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_lock_data_dir_is_disabled(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "LOCK DATA DIRECTORY") + assert False + except: + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/replication_disabled.py b/tests/e2e/disk_storage/replication_disabled.py new file mode 100644 index 000000000..1fd0237d7 --- /dev/null +++ b/tests/e2e/disk_storage/replication_disabled.py @@ -0,0 +1,29 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_replication_is_disabled(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "SET REPLICATION ROLE TO MAIN") + assert False + except: + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/snapshot_disabled.py b/tests/e2e/disk_storage/snapshot_disabled.py new file mode 100644 index 000000000..575b9f381 --- /dev/null +++ b/tests/e2e/disk_storage/snapshot_disabled.py @@ -0,0 +1,29 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_snapshot_is_disabled(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "CREATE SNAPSHOT") + assert False + except: + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/update_storage_mode_db_not_empty.py b/tests/e2e/disk_storage/update_storage_mode_db_not_empty.py new file mode 100644 index 000000000..ee61e2185 --- /dev/null +++ b/tests/e2e/disk_storage/update_storage_mode_db_not_empty.py @@ -0,0 +1,31 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_forbid_switching_from_memory_to_disk_when_database_is_not_empty(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "CREATE (:DiskLabel {id: 1})") + try: + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + assert False + except: + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n") + execute_and_fetch_all(cursor, "FREE MEMORY") # to enforce garbage collection + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/update_storage_mode_disk_to_memory.py b/tests/e2e/disk_storage/update_storage_mode_disk_to_memory.py new file mode 100644 index 000000000..bf8bf55ba --- /dev/null +++ b/tests/e2e/disk_storage/update_storage_mode_disk_to_memory.py @@ -0,0 +1,29 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_forbid_switching_from_disk_to_memory(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + try: + execute_and_fetch_all(cursor, "STORAGE MODE IN_MEMORY_TRANSACTIONAL") + assert False + except: + assert True + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/update_storage_mode_memory_to_disk.py b/tests/e2e/disk_storage/update_storage_mode_memory_to_disk.py new file mode 100644 index 000000000..2fb7fc6ce --- /dev/null +++ b/tests/e2e/disk_storage/update_storage_mode_memory_to_disk.py @@ -0,0 +1,24 @@ +# Copyright 2023 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import connect, execute_and_fetch_all + + +def test_allow_switching_from_memory_to_disk_when_database_is_empty(connect): + cursor = connect.cursor() + execute_and_fetch_all(cursor, "STORAGE MODE ON_DISK_TRANSACTIONAL") + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/disk_storage/workloads.yaml b/tests/e2e/disk_storage/workloads.yaml new file mode 100644 index 000000000..4f449e77d --- /dev/null +++ b/tests/e2e/disk_storage/workloads.yaml @@ -0,0 +1,53 @@ +disk_storage: &disk_storage + cluster: + main: + args: ["--bolt-port", "7687", "--log-level", "TRACE", "--memory-limit", "50"] + log_file: "disk_storage.log" + setup_queries: [] + validation_queries: [] + +workloads: + - name: "Test that loading vertices from indices and creating edge with them works." + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/create_edge_from_indices.py"] + <<: *disk_storage + + - name: "Test that free memory query is disabled with on-disk storage." + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/free_memory_disabled.py"] + <<: *disk_storage + + - name: "Test that replication queries are disabled with on-disk storage." + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/replication_disabled.py"] + <<: *disk_storage + + - name: "Test that create snapshot queries are disabled with on-disk storage." + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/snapshot_disabled.py"] + <<: *disk_storage + + - name: "Test that lock data directory query is disabled with on-disk storage." + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/lock_data_dir_disabled.py"] + <<: *disk_storage + + - name: "Tests importing data on disk " + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/data_import.py"] + <<: *disk_storage + + - name: "Tests when switching from in-memory storage to disk storage when the db isn't empty. " + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/update_storage_mode_db_not_empty.py"] + <<: *disk_storage + + - name: "Tests when switching from disk storage to in-memory storage is forbidden. " + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/update_storage_mode_disk_to_memory.py"] + <<: *disk_storage + + - name: "Tests when switching from in-memory to disk is allowed. " + binary: "tests/e2e/pytest_runner.sh" + args: ["disk_storage/update_storage_mode_memory_to_disk.py"] + <<: *disk_storage diff --git a/tests/e2e/isolation_levels/isolation_levels.cpp b/tests/e2e/isolation_levels/isolation_levels.cpp index 8b109fd18..4cec5b13d 100644 --- a/tests/e2e/isolation_levels/isolation_levels.cpp +++ b/tests/e2e/isolation_levels/isolation_levels.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -40,6 +40,19 @@ auto GetVertexCount(std::unique_ptr<mg::Client> &client) { return row[0].ValueInt(); } +bool IsDiskStorageMode(std::unique_ptr<mg::Client> &client) { + MG_ASSERT(client->Execute("SHOW STORAGE INFO")); + auto maybe_rows = client->FetchAll(); + MG_ASSERT(maybe_rows, "Failed to fetch storage info"); + + for (auto &row : *maybe_rows) { + if (row[0].ValueString() == "storage_mode") { + return row[1].ValueString() == "ON_DISK_TRANSACTIONAL"; + } + } + return false; +} + void CleanDatabase() { auto client = GetClient(); MG_ASSERT(client->Execute("MATCH (n) DETACH DELETE n;")); @@ -146,15 +159,22 @@ inline constexpr std::array isolation_levels{std::pair{"SNAPSHOT ISOLATION", &Te std::pair{"READ COMMITTED", &TestReadCommitted}, std::pair{"READ UNCOMMITTED", &TestReadUncommitted}}; -void TestGlobalIsolationLevel() { +void TestGlobalIsolationLevel(bool isDiskStorage) { spdlog::info("\n\n----Test global isolation levels----\n"); auto first_client = GetClient(); auto second_client = GetClient(); for (const auto &[isolation_level, verification_function] : isolation_levels) { spdlog::info("--------------------------"); + + if (isDiskStorage && strcmp(isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported isolation level {}", isolation_level); + continue; + } + spdlog::info("Setting global isolation level to {}", isolation_level); MG_ASSERT(first_client->Execute(fmt::format("SET GLOBAL TRANSACTION ISOLATION LEVEL {}", isolation_level))); + first_client->DiscardAll(); verification_function(first_client); @@ -163,18 +183,26 @@ void TestGlobalIsolationLevel() { } } -void TestSessionIsolationLevel() { +void TestSessionIsolationLevel(bool isDiskStorage) { spdlog::info("\n\n----Test session isolation levels----\n"); auto global_client = GetClient(); auto session_client = GetClient(); for (const auto &[global_isolation_level, global_verification_function] : isolation_levels) { + if (isDiskStorage && strcmp(global_isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported global isolation level {}", global_isolation_level); + continue; + } spdlog::info("Setting global isolation level to {}", global_isolation_level); MG_ASSERT(global_client->Execute(fmt::format("SET GLOBAL TRANSACTION ISOLATION LEVEL {}", global_isolation_level))); global_client->DiscardAll(); for (const auto &[session_isolation_level, session_verification_function] : isolation_levels) { spdlog::info("--------------------------"); + if (isDiskStorage && strcmp(session_isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported session isolation level {}", session_isolation_level); + continue; + } spdlog::info("Setting session isolation level to {}", session_isolation_level); MG_ASSERT( session_client->Execute(fmt::format("SET SESSION TRANSACTION ISOLATION LEVEL {}", session_isolation_level))); @@ -190,17 +218,26 @@ void TestSessionIsolationLevel() { } // Priority of applying the isolation level from highest priority NEXT -> SESSION -> GLOBAL -void TestNextIsolationLevel() { +void TestNextIsolationLevel(bool isDiskStorage) { spdlog::info("\n\n----Test next isolation levels----\n"); auto global_client = GetClient(); auto session_client = GetClient(); for (const auto &[global_isolation_level, global_verification_function] : isolation_levels) { + if (isDiskStorage && strcmp(global_isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported global isolation level {}", global_isolation_level); + continue; + } spdlog::info("Setting global isolation level to {}", global_isolation_level); + MG_ASSERT(global_client->Execute(fmt::format("SET GLOBAL TRANSACTION ISOLATION LEVEL {}", global_isolation_level))); global_client->DiscardAll(); for (const auto &[session_isolation_level, session_verification_function] : isolation_levels) { + if (isDiskStorage && strcmp(session_isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported session isolation level {}", session_isolation_level); + continue; + } spdlog::info("Setting session isolation level to {}", session_isolation_level); MG_ASSERT( session_client->Execute(fmt::format("SET SESSION TRANSACTION ISOLATION LEVEL {}", session_isolation_level))); @@ -213,6 +250,11 @@ void TestNextIsolationLevel() { spdlog::info("Verifying client which is using session isolation level"); session_verification_function(session_client); + if (isDiskStorage && strcmp(next_isolation_level, "SNAPSHOT ISOLATION") != 0) { + spdlog::info("Skipping for disk storage unsupported next transaction isolation level {}", + next_isolation_level); + continue; + } spdlog::info("Setting isolation level of the next transaction to {}", next_isolation_level); MG_ASSERT(global_client->Execute(fmt::format("SET NEXT TRANSACTION ISOLATION LEVEL {}", next_isolation_level))); global_client->DiscardAll(); @@ -244,9 +286,13 @@ int main(int argc, char **argv) { mg::Client::Init(); - TestGlobalIsolationLevel(); - TestSessionIsolationLevel(); - TestNextIsolationLevel(); + auto client = GetClient(); + bool isDiskStorage = IsDiskStorageMode(client); + client->DiscardAll(); + + TestGlobalIsolationLevel(isDiskStorage); + TestSessionIsolationLevel(isDiskStorage); + TestNextIsolationLevel(isDiskStorage); return 0; } diff --git a/tests/e2e/isolation_levels/workloads.yaml b/tests/e2e/isolation_levels/workloads.yaml index 5d793396e..cca4693ab 100644 --- a/tests/e2e/isolation_levels/workloads.yaml +++ b/tests/e2e/isolation_levels/workloads.yaml @@ -6,9 +6,21 @@ template_cluster: &template_cluster log_file: "isolation-levels-e2e.log" setup_queries: [] validation_queries: [] +disk_cluster: &disk_cluster + cluster: + main: + args: ["--bolt-port", *bolt_port, "--log-level=TRACE"] + log_file: "isolation-levels-disk-e2e.log" + setup_queries: ["storage mode on_disk_transactional"] + validation_queries: [] workloads: - name: "Isolation levels" binary: "tests/e2e/isolation_levels/memgraph__e2e__isolation_levels" args: ["--bolt-port", *bolt_port] <<: *template_cluster + + - name: "Isolation levels for disk storage" + binary: "tests/e2e/isolation_levels/memgraph__e2e__isolation_levels" + args: ["--bolt-port", *bolt_port] + <<: *disk_cluster diff --git a/tests/e2e/magic_functions/workloads.yaml b/tests/e2e/magic_functions/workloads.yaml index 1f130099d..a2346b373 100644 --- a/tests/e2e/magic_functions/workloads.yaml +++ b/tests/e2e/magic_functions/workloads.yaml @@ -5,6 +5,13 @@ template_cluster: &template_cluster log_file: "magic-functions-e2e.log" setup_queries: [] validation_queries: [] +disk_cluster: &disk_cluster + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE"] + log_file: "magic-functions-e2e.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] workloads: - name: "Magic functions runner" @@ -12,3 +19,9 @@ workloads: proc: "tests/e2e/magic_functions/functions/" args: ["magic_functions/function_example.py"] <<: *template_cluster + + - name: "Magic functions runner for disk storage" + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/magic_functions/functions/" + args: ["magic_functions/function_example.py"] + <<: *disk_cluster diff --git a/tests/e2e/memory/workloads.yaml b/tests/e2e/memory/workloads.yaml index bf7ba373e..88573c761 100644 --- a/tests/e2e/memory/workloads.yaml +++ b/tests/e2e/memory/workloads.yaml @@ -6,6 +6,14 @@ template_cluster: &template_cluster log_file: "memory-e2e.log" setup_queries: [] validation_queries: [] +disk_cluster: &disk_cluster + cluster: + main: + args: ["--bolt-port", *bolt_port, "--memory-limit=1000", "--storage-gc-cycle-sec=180", "--log-level=TRACE"] + log_file: "memory-e2e.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] + workloads: - name: "Memory control" diff --git a/tests/e2e/mock_api/workloads.yaml b/tests/e2e/mock_api/workloads.yaml index 165477479..f4b638a11 100644 --- a/tests/e2e/mock_api/workloads.yaml +++ b/tests/e2e/mock_api/workloads.yaml @@ -74,8 +74,91 @@ compare_mock: &compare_mock - "MATCH (u) SET u.permanent_id = u.__mg_id__;" - "MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;" validation_queries: [] +disk_cluster: &disk_cluster + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"] + log_file: "test-compare-mock-e2e.log" + setup_queries: + - "STORAGE MODE ON_DISK_TRANSACTIONAL;" + - "CREATE INDEX ON :__mg_vertex__(__mg_id__);" + - "CREATE (:__mg_vertex__:`Person` {__mg_id__: 0, `name`: 'Peter', `surname`: 'Yang'});" + - "CREATE (:__mg_vertex__:`Team` {__mg_id__: 1, `name`: 'Engineering'});" + - "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 2, `name`: 'Memgraph'});" + - "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 3, `name`: 'MAGE'});" + - "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 4, `name`: 'GQLAlchemy'});" + - "CREATE (:__mg_vertex__:`Company`:`Startup` {__mg_id__: 5, `name`: 'Memgraph'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 6, `name`: 'welcome_to_engineering.txt'});" + - "CREATE (:__mg_vertex__:`Storage` {__mg_id__: 7, `name`: 'Google Drive'});" + - "CREATE (:__mg_vertex__:`Storage` {__mg_id__: 8, `name`: 'Notion'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 9, `name`: 'welcome_to_memgraph.txt'});" + - "CREATE (:__mg_vertex__:`Person` {__mg_id__: 10, `name`: 'Carl'});" + - "CREATE (:__mg_vertex__:`Folder` {__mg_id__: 11, `name`: 'engineering_folder'});" + - "CREATE (:__mg_vertex__:`Person` {__mg_id__: 12, `name`: 'Anna'});" + - "CREATE (:__mg_vertex__:`Folder` {__mg_id__: 13, `name`: 'operations_folder'});" + - "CREATE (:__mg_vertex__:`Team` {__mg_id__: 14, `name`: 'Operations'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 15, `name`: 'operations101.txt'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 16, `name`: 'expenses2022.csv'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 17, `name`: 'salaries2022.csv'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 18, `name`: 'engineering101.txt'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 19, `name`: 'working_with_github.txt'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 20, `name`: 'working_with_notion.txt'});" + - "CREATE (:__mg_vertex__:`Team` {__mg_id__: 21, `name`: 'Marketing'});" + - "CREATE (:__mg_vertex__:`Person` {__mg_id__: 22, `name`: 'Julie'});" + - "CREATE (:__mg_vertex__:`Account` {__mg_id__: 23, `name`: 'Facebook'});" + - "CREATE (:__mg_vertex__:`Account` {__mg_id__: 24, `name`: 'LinkedIn'});" + - "CREATE (:__mg_vertex__:`Account` {__mg_id__: 25, `name`: 'HackerNews'});" + - "CREATE (:__mg_vertex__:`File` {__mg_id__: 26, `name`: 'welcome_to_marketing.txt'});" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 0}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 1}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 2}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 14 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 3}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 4}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 3 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 5}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 4 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 6}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 6 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 7}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 11 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 8}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 1 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 9}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 21 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 10}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 14 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 11}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 12}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 8 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 13}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 12 CREATE (u)-[:`CREATED_BY` {`permanent_id`: 14}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 1 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 15}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 16}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 17}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 7 CREATE(u)-[:`IS_STORED_IN` {`permanent_id`: 18}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 18 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 19}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 19 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 20}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 20 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 21}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 14 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 22}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 15 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 23}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 16 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 24}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 17 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 25}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 26}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 27}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 23 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 28}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 24 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 29}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 25 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 30}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 26 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 31}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 21 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 32}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 33}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 34}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 26 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 35}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 26 AND v.__mg_id__ = 8 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 36}]->(v);" + - "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 1 CREATE (u)-[:`HAS_TEAM_2` {`importance`: 'HIGH', `permanent_id`: 37}]->(v);" + - "DROP INDEX ON :__mg_vertex__(__mg_id__);" + - "MATCH (u) SET u.permanent_id = u.__mg_id__;" + - "MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;" + validation_queries: [] workloads: + - name: "test-compare-mock on disk" # should be the same as the python file + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/mock_api/procedures/" + args: ["mock_api/test_compare_mock.py"] + <<: *disk_cluster + - name: "test-compare-mock" # should be the same as the python file binary: "tests/e2e/pytest_runner.sh" proc: "tests/e2e/mock_api/procedures/" diff --git a/tests/e2e/python_query_modules_reloading/workloads.yaml b/tests/e2e/python_query_modules_reloading/workloads.yaml index 82f91644b..56cd65ff9 100644 --- a/tests/e2e/python_query_modules_reloading/workloads.yaml +++ b/tests/e2e/python_query_modules_reloading/workloads.yaml @@ -5,6 +5,14 @@ test_reload_query_module: &test_reload_query_module log_file: "py-query-modules-reloading-e2e.log" setup_queries: [] validation_queries: [] +disk_test_reload_query_module: &disk_test_reload_query_module + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"] + log_file: "py-query-modules-reloading-e2e.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] + workloads: - name: "test-reload-query-module" # should be the same as the python file @@ -12,3 +20,8 @@ workloads: proc: "tests/e2e/python_query_modules_reloading/procedures/" args: ["python_query_modules_reloading/test_reload_query_module.py"] <<: *test_reload_query_module + - name: "test-reload-query-module on disk" # should be the same as the python file + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/python_query_modules_reloading/procedures/" + args: ["python_query_modules_reloading/test_reload_query_module.py"] + <<: *disk_test_reload_query_module diff --git a/tests/e2e/run_e2e.sh b/tests/e2e/run_e2e.sh new file mode 100644 index 000000000..55c376941 --- /dev/null +++ b/tests/e2e/run_e2e.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# TODO: andi as a side project +python3 runner.py --workloads-root-directory disk_storage diff --git a/tests/e2e/runner.py b/tests/e2e/runner.py index 56e05733a..e3be4afa2 100755 --- a/tests/e2e/runner.py +++ b/tests/e2e/runner.py @@ -13,6 +13,7 @@ import atexit import logging import os import subprocess +import time from argparse import ArgumentParser from pathlib import Path @@ -30,6 +31,7 @@ def load_args(): parser = ArgumentParser() parser.add_argument("--workloads-root-directory", required=True) parser.add_argument("--workload-name", default=None, required=False) + parser.add_argument("--debug", default=False, required=False) return parser.parse_args() @@ -60,6 +62,11 @@ def run(args): procdir = os.path.join(BUILD_DIR, workload["proc"]) interactive_mg_runner.start_all(workload["cluster"], procdir) + if args.debug: + hosts = subprocess.check_output("pgrep memgraph", shell=True) + print(f"PID: {hosts}") + time.sleep(10) + # Test. mg_test_binary = os.path.join(BUILD_DIR, workload["binary"]) subprocess.run([mg_test_binary] + workload["args"], check=True, stderr=subprocess.STDOUT) diff --git a/tests/e2e/temporal_types/workloads.yaml b/tests/e2e/temporal_types/workloads.yaml index 359edb714..f4bccefae 100644 --- a/tests/e2e/temporal_types/workloads.yaml +++ b/tests/e2e/temporal_types/workloads.yaml @@ -6,9 +6,21 @@ template_cluster: &template_cluster log_file: "temporal-types-e2e.log" setup_queries: [] validation_queries: [] +disk_template_cluster: &disk_template_cluster + cluster: + main: + args: ["--bolt_port", *bolt_port, "--log-level=TRACE"] + log_file: "temporal-types-e2e.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] + workloads: - name: "Temporal" binary: "tests/e2e/temporal_types/memgraph__e2e__temporal_roundtrip" args: ["--bolt_port", *bolt_port] <<: *template_cluster + - name: "Temporal on disk" + binary: "tests/e2e/temporal_types/memgraph__e2e__temporal_roundtrip" + args: ["--bolt_port", *bolt_port] + <<: *disk_template_cluster diff --git a/tests/e2e/transaction_queue/workloads.yaml b/tests/e2e/transaction_queue/workloads.yaml index b5f15facf..d73c2ad13 100644 --- a/tests/e2e/transaction_queue/workloads.yaml +++ b/tests/e2e/transaction_queue/workloads.yaml @@ -5,6 +5,14 @@ test_transaction_queue: &test_transaction_queue log_file: "transaction_queue.log" setup_queries: [] validation_queries: [] +disk_test_transaction_queue: &disk_test_transaction_queue + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"] + log_file: "transaction_queue.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] + workloads: - name: "test-transaction-queue" # should be the same as the python file @@ -12,3 +20,8 @@ workloads: proc: "tests/e2e/transaction_queue/procedures/" args: ["transaction_queue/test_transaction_queue.py"] <<: *test_transaction_queue + - name: "test-transaction-queue on disk" # should be the same as the python file + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/transaction_queue/procedures/" + args: ["transaction_queue/test_transaction_queue.py"] + <<: *disk_test_transaction_queue diff --git a/tests/e2e/triggers/workloads.yaml b/tests/e2e/triggers/workloads.yaml index a80a8f2fa..7f6cd7743 100644 --- a/tests/e2e/triggers/workloads.yaml +++ b/tests/e2e/triggers/workloads.yaml @@ -13,6 +13,20 @@ storage_properties_edges_false: &storage_properties_edges_false log_file: "triggers-e2e.log" setup_queries: [] validation_queries: [] +disk_template_cluster: &disk_template_cluster + cluster: + main: + args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--storage-properties-on-edges=True"] + log_file: "triggers-e2e.log" + setup_queries: [] + validation_queries: [] +disk_storage_properties_edges_false: &disk_storage_properties_edges_false + cluster: + main: + args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--also-log-to-stderr", "--storage-properties-on-edges=False"] + log_file: "triggers-e2e.log" + setup_queries: [] + validation_queries: [] workloads: - name: "ON CREATE Triggers" @@ -39,3 +53,28 @@ workloads: proc: "tests/e2e/triggers/procedures/" args: ["triggers/triggers_properties_false.py"] <<: *storage_properties_edges_false + + - name: "ON CREATE Triggers on disk" + binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_create" + args: ["--bolt-port", *bolt_port] + proc: "tests/e2e/triggers/procedures/" + <<: *disk_template_cluster + - name: "ON UPDATE Triggers on disk" + binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_update" + args: ["--bolt-port", *bolt_port] + proc: "tests/e2e/triggers/procedures/" + <<: *disk_template_cluster + - name: "ON DELETE Triggers Storage Properties On Edges True On Disk" + binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete" + args: ["--bolt-port", *bolt_port] + proc: "tests/e2e/triggers/procedures/" + <<: *disk_template_cluster + - name: "Triggers privilege check on disk" + binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges" + args: ["--bolt-port", *bolt_port] + <<: *disk_template_cluster + - name: "ON DELETE Triggers Storage Properties On Edges False On Disk" # should be the same as the python file + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/triggers/procedures/" + args: ["triggers/triggers_properties_false.py"] + <<: *disk_storage_properties_edges_false diff --git a/tests/e2e/write_procedures/workloads.yaml b/tests/e2e/write_procedures/workloads.yaml index 667947920..be03d0cab 100644 --- a/tests/e2e/write_procedures/workloads.yaml +++ b/tests/e2e/write_procedures/workloads.yaml @@ -5,6 +5,13 @@ template_cluster: &template_cluster log_file: "write-procedures-e2e.log" setup_queries: [] validation_queries: [] +disk_template_cluster: &disk_template_cluster + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE"] + log_file: "write-procedures-e2e.log" + setup_queries: ["STORAGE MODE ON_DISK_TRANSACTIONAL"] + validation_queries: [] workloads: - name: "Write procedures simple" @@ -17,3 +24,13 @@ workloads: proc: "tests/e2e/write_procedures/procedures/" args: ["write_procedures/read_subgraph.py"] <<: *template_cluster + - name: "Write procedures simple on disk" + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/write_procedures/procedures/" + args: ["write_procedures/simple_write.py"] + <<: *disk_template_cluster + - name: "Graph projection procedures on disk" + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/write_procedures/procedures/" + args: ["write_procedures/read_subgraph.py"] + <<: *disk_template_cluster diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index a8b66ff26..cbcbe38b8 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -27,7 +27,6 @@ #include "query/plan/planner.hpp" #include "query/plan/pretty_print.hpp" #include "query/typed_value.hpp" -#include "storage/v2/indices.hpp" #include "storage/v2/property_value.hpp" #include "utils/string.hpp" diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp index 67875734b..700b70a7a 100644 --- a/tests/manual/query_planner.cpp +++ b/tests/manual/query_planner.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,16 +13,16 @@ #include <gflags/gflags.h> -#include "storage/v2/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" DECLARE_int32(min_log_level); int main(int argc, char *argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); spdlog::set_level(spdlog::level::err); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); RunInteractivePlanning(&dba); return 0; } diff --git a/tests/manual/single_query.cpp b/tests/manual/single_query.cpp index 96407c41b..cb0bf6f80 100644 --- a/tests/manual/single_query.cpp +++ b/tests/manual/single_query.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,8 +13,9 @@ #include "license/license.hpp" #include "query/config.hpp" #include "query/interpreter.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" -#include "storage/v2/storage.hpp" #include "utils/on_scope_exit.hpp" int main(int argc, char *argv[]) { @@ -26,15 +27,15 @@ int main(int argc, char *argv[]) { exit(1); } - memgraph::storage::Storage db; auto data_directory = std::filesystem::temp_directory_path() / "single_query_test"; memgraph::utils::OnScopeExit([&data_directory] { std::filesystem::remove_all(data_directory); }); memgraph::license::global_license_checker.EnableTesting(); - memgraph::query::InterpreterContext interpreter_context{&db, memgraph::query::InterpreterConfig{}, data_directory}; + memgraph::query::InterpreterContext interpreter_context{memgraph::storage::Config{}, + memgraph::query::InterpreterConfig{}, data_directory}; memgraph::query::Interpreter interpreter{&interpreter_context}; - ResultStreamFaker stream(&db); + ResultStreamFaker stream(interpreter_context.db.get()); auto [header, _, qid] = interpreter.Prepare(argv[1], {}, nullptr); stream.Header(header); auto summary = interpreter.PullAll(&stream); diff --git a/tests/property_based/random_graph.cpp b/tests/property_based/random_graph.cpp index 909381b9c..5beb22930 100644 --- a/tests/property_based/random_graph.cpp +++ b/tests/property_based/random_graph.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -9,6 +9,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include <memory> #include <unordered_map> #include <vector> @@ -19,7 +20,9 @@ #include <rapidcheck.h> #include <rapidcheck/gtest.h> +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" +#include "storage/v2/vertex_accessor.hpp" /** * It is possible to run test with custom seed with: @@ -32,45 +35,45 @@ RC_GTEST_PROP(RandomGraph, RandomGraph, (std::vector<std::string> vertex_labels, int vertices_num = vertex_labels.size(); int edges_num = edge_types.size(); - memgraph::storage::Storage db; + std::unique_ptr<memgraph::storage::Storage> db{new memgraph::storage::InMemoryStorage()}; std::vector<memgraph::storage::VertexAccessor> vertices; std::unordered_map<memgraph::storage::VertexAccessor, std::string> vertex_label_map; std::unordered_map<memgraph::storage::EdgeAccessor, std::string> edge_type_map; - auto dba = db.Access(); + auto dba = db->Access(); for (auto label : vertex_labels) { - auto vertex_accessor = dba.CreateVertex(); - RC_ASSERT(vertex_accessor.AddLabel(dba.NameToLabel(label)).HasValue()); - vertex_label_map.insert({vertex_accessor, label}); + auto vertex_accessor = dba->CreateVertex(); + RC_ASSERT(vertex_accessor.AddLabel(dba->NameToLabel(label)).HasValue()); + vertex_label_map.emplace(vertex_accessor, label); vertices.push_back(vertex_accessor); } for (auto type : edge_types) { auto &from = vertices[*rc::gen::inRange(0, vertices_num)]; auto &to = vertices[*rc::gen::inRange(0, vertices_num)]; - auto maybe_edge_accessor = dba.CreateEdge(&from, &to, dba.NameToEdgeType(type)); + auto maybe_edge_accessor = dba->CreateEdge(&from, &to, dba->NameToEdgeType(type)); RC_ASSERT(maybe_edge_accessor.HasValue()); edge_type_map.insert({*maybe_edge_accessor, type}); } - dba.AdvanceCommand(); + dba->AdvanceCommand(); int edges_num_check = 0; int vertices_num_check = 0; - for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + for (auto vertex : dba->Vertices(memgraph::storage::View::OLD)) { auto label = vertex_label_map.at(vertex); auto maybe_labels = vertex.Labels(memgraph::storage::View::OLD); RC_ASSERT(maybe_labels.HasValue()); const auto &labels = *maybe_labels; RC_ASSERT(labels.size() == 1); - RC_ASSERT(dba.LabelToName(labels[0]) == label); + RC_ASSERT(dba->LabelToName(labels[0]) == label); vertices_num_check++; auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD); RC_ASSERT(maybe_edges.HasValue()); for (auto &edge : *maybe_edges) { const auto &type = edge_type_map.at(edge); - RC_ASSERT(dba.EdgeTypeToName(edge.EdgeType()) == type); + RC_ASSERT(dba->EdgeTypeToName(edge.EdgeType()) == type); edges_num_check++; } } diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 411c5571c..1c69d2606 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -303,11 +303,23 @@ target_link_libraries(${test_prefix}storage_v2_constraints mg-storage-v2) add_unit_test(storage_v2_decoder_encoder.cpp) target_link_libraries(${test_prefix}storage_v2_decoder_encoder mg-storage-v2) -add_unit_test(storage_v2_durability.cpp) -target_link_libraries(${test_prefix}storage_v2_durability mg-storage-v2) +add_unit_test(storage_v2_durability_inmemory.cpp) +target_link_libraries(${test_prefix}storage_v2_durability_inmemory mg-storage-v2) -add_unit_test(storage_v2_edge.cpp) -target_link_libraries(${test_prefix}storage_v2_edge mg-storage-v2) +add_unit_test(storage_rocks.cpp) +target_link_libraries(${test_prefix}storage_rocks mg-storage-v2) + +add_unit_test(storage_v2_disk.cpp) +target_link_libraries(${test_prefix}storage_v2_disk mg-storage-v2) + +add_unit_test(clearing_old_disk_data.cpp) +target_link_libraries(${test_prefix}clearing_old_disk_data mg-storage-v2) + +add_unit_test(storage_v2_edge_inmemory.cpp) +target_link_libraries(${test_prefix}storage_v2_edge_inmemory mg-storage-v2) + +add_unit_test(storage_v2_edge_ondisk.cpp) +target_link_libraries(${test_prefix}storage_v2_edge_ondisk mg-storage-v2) add_unit_test(storage_v2_gc.cpp) target_link_libraries(${test_prefix}storage_v2_gc mg-storage-v2) diff --git a/tests/unit/auth_checker.cpp b/tests/unit/auth_checker.cpp index 9028f0d3c..b048e9491 100644 --- a/tests/unit/auth_checker.cpp +++ b/tests/unit/auth_checker.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,30 +13,38 @@ #include <gtest/gtest.h> #include "auth/models.hpp" +#include "disk_test_utils.hpp" #include "glue/auth_checker.hpp" #include "license/license.hpp" #include "query_plan_common.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/view.hpp" #ifdef MG_ENTERPRISE +template <typename StorageType> class FineGrainedAuthCheckerFixture : public testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + const std::string testSuite = "auth_checker"; + + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2) memgraph::query::VertexAccessor v1{dba.InsertVertex()}; memgraph::query::VertexAccessor v2{dba.InsertVertex()}; memgraph::query::VertexAccessor v3{dba.InsertVertex()}; - memgraph::storage::EdgeTypeId edge_type_one{db.NameToEdgeType("edge_type_1")}; - memgraph::storage::EdgeTypeId edge_type_two{db.NameToEdgeType("edge_type_2")}; + memgraph::storage::EdgeTypeId edge_type_one{db->NameToEdgeType("edge_type_1")}; + memgraph::storage::EdgeTypeId edge_type_two{db->NameToEdgeType("edge_type_2")}; - memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type_one)}; - memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v1, &v3, edge_type_one)}; - memgraph::query::EdgeAccessor r3{*dba.InsertEdge(&v1, &v2, edge_type_two)}; - memgraph::query::EdgeAccessor r4{*dba.InsertEdge(&v1, &v3, edge_type_two)}; + memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&this->v1, &this->v2, edge_type_one)}; + memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&this->v1, &this->v3, edge_type_one)}; + memgraph::query::EdgeAccessor r3{*dba.InsertEdge(&this->v1, &this->v2, edge_type_two)}; + memgraph::query::EdgeAccessor r4{*dba.InsertEdge(&this->v1, &this->v3, edge_type_two)}; void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); @@ -45,167 +53,176 @@ class FineGrainedAuthCheckerFixture : public testing::Test { ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l3")).HasValue()); dba.AdvanceCommand(); } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } }; -TEST_F(FineGrainedAuthCheckerFixture, GrantedAllLabels) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(FineGrainedAuthCheckerFixture, StorageTypes); + +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantedAllLabels) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) { +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; - ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_TRUE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_TRUE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_TRUE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, DeniedAllLabels) { +TYPED_TEST(FineGrainedAuthCheckerFixture, DeniedAllLabels) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; ASSERT_FALSE( - auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) { +TYPED_TEST(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; - ASSERT_FALSE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_FALSE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_FALSE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_FALSE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, GrantLabel) { +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantLabel) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, DenyLabel) { +TYPED_TEST(FineGrainedAuthCheckerFixture, DenyLabel) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) { +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, MultipleVertexLabels) { +TYPED_TEST(FineGrainedAuthCheckerFixture, MultipleVertexLabels) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l3")).HasValue()); - ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l1")).HasValue()); - dba.AdvanceCommand(); + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; + ASSERT_TRUE(this->v1.AddLabel(this->dba.NameToLabel("l3")).HasValue()); + ASSERT_TRUE(this->v2.AddLabel(this->dba.NameToLabel("l1")).HasValue()); + this->dba.AdvanceCommand(); ASSERT_FALSE( - auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_FALSE( - auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); ASSERT_TRUE( - auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + auth_checker.Has(this->v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, GrantEdgeType) { +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantEdgeType) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant( "edge_type_1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; - ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, DenyEdgeType) { +TYPED_TEST(FineGrainedAuthCheckerFixture, DenyEdgeType) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; - ASSERT_FALSE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } -TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificEdgeTypes) { +TYPED_TEST(FineGrainedAuthCheckerFixture, GrantAndDenySpecificEdgeTypes) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant( "edge_type_1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_2", memgraph::auth::FineGrainedPermission::NOTHING); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; - ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_TRUE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_FALSE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); - ASSERT_FALSE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_TRUE(auth_checker.Has(this->r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); + ASSERT_FALSE(auth_checker.Has(this->r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)); } #endif diff --git a/tests/unit/bfs_common.hpp b/tests/unit/bfs_common.hpp index 431ab9214..e11fd507d 100644 --- a/tests/unit/bfs_common.hpp +++ b/tests/unit/bfs_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -200,22 +200,6 @@ enum class FineGrainedTestType { LABEL_3_DENIED }; -// Common interface for single-node and distributed Memgraph. -class Database { - public: - virtual memgraph::storage::Storage::Accessor Access() = 0; - virtual std::unique_ptr<memgraph::query::plan::LogicalOperator> MakeBfsOperator( - memgraph::query::Symbol source_sym, memgraph::query::Symbol sink_sym, memgraph::query::Symbol edge_sym, - memgraph::query::EdgeAtom::Direction direction, const std::vector<memgraph::storage::EdgeTypeId> &edge_types, - const std::shared_ptr<memgraph::query::plan::LogicalOperator> &input, bool existing_node, - memgraph::query::Expression *lower_bound, memgraph::query::Expression *upper_bound, - const memgraph::query::plan::ExpansionLambda &filter_lambda) = 0; - virtual std::pair<std::vector<memgraph::query::VertexAccessor>, std::vector<memgraph::query::EdgeAccessor>> - BuildGraph(memgraph::query::DbAccessor *dba, const std::vector<int> &vertex_locations, - const std::vector<std::tuple<int, int, std::string>> &edges) = 0; - virtual ~Database() {} -}; - // Returns an operator that yields vertices given by their address. We will also // include memgraph::query::TypedValue() to account for the optional match case. std::unique_ptr<memgraph::query::plan::LogicalOperator> YieldVertices( @@ -291,431 +275,453 @@ std::vector<std::vector<int>> CheckPathsAndExtractDistances( return distances; } -void BfsTest(Database *db, int lower_bound, int upper_bound, memgraph::query::EdgeAtom::Direction direction, - std::vector<std::string> edge_types, bool known_sink, FilterLambdaType filter_lambda_type) { - auto storage_dba = db->Access(); - memgraph::query::DbAccessor dba(&storage_dba); - memgraph::query::AstStorage storage; - memgraph::query::ExecutionContext context{&dba}; - memgraph::query::Symbol blocked_sym = context.symbol_table.CreateSymbol("blocked", true); - memgraph::query::Symbol source_sym = context.symbol_table.CreateSymbol("source", true); - memgraph::query::Symbol sink_sym = context.symbol_table.CreateSymbol("sink", true); - memgraph::query::Symbol edges_sym = context.symbol_table.CreateSymbol("edges", true); - memgraph::query::Symbol inner_node_sym = context.symbol_table.CreateSymbol("inner_node", true); - memgraph::query::Symbol inner_edge_sym = context.symbol_table.CreateSymbol("inner_edge", true); - memgraph::query::Identifier *blocked = IDENT("blocked")->MapTo(blocked_sym); - memgraph::query::Identifier *inner_node = IDENT("inner_node")->MapTo(inner_node_sym); - memgraph::query::Identifier *inner_edge = IDENT("inner_edge")->MapTo(inner_edge_sym); +// Common interface for single-node and distributed Memgraph. +class Database { + public: + virtual std::unique_ptr<memgraph::storage::Storage::Accessor> Access() = 0; + virtual std::unique_ptr<memgraph::query::plan::LogicalOperator> MakeBfsOperator( + memgraph::query::Symbol source_sym, memgraph::query::Symbol sink_sym, memgraph::query::Symbol edge_sym, + memgraph::query::EdgeAtom::Direction direction, const std::vector<memgraph::storage::EdgeTypeId> &edge_types, + const std::shared_ptr<memgraph::query::plan::LogicalOperator> &input, bool existing_node, + memgraph::query::Expression *lower_bound, memgraph::query::Expression *upper_bound, + const memgraph::query::plan::ExpansionLambda &filter_lambda) = 0; + virtual std::pair<std::vector<memgraph::query::VertexAccessor>, std::vector<memgraph::query::EdgeAccessor>> + BuildGraph(memgraph::query::DbAccessor *dba, const std::vector<int> &vertex_locations, + const std::vector<std::tuple<int, int, std::string>> &edges) = 0; + virtual ~Database() {} - std::vector<memgraph::query::VertexAccessor> vertices; - std::vector<memgraph::query::EdgeAccessor> edges; + void BfsTest(Database *db, int lower_bound, int upper_bound, memgraph::query::EdgeAtom::Direction direction, + std::vector<std::string> edge_types, bool known_sink, FilterLambdaType filter_lambda_type) { + auto storage_dba = db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); + memgraph::query::ExecutionContext context{&dba}; + memgraph::query::Symbol blocked_sym = context.symbol_table.CreateSymbol("blocked", true); + memgraph::query::Symbol source_sym = context.symbol_table.CreateSymbol("source", true); + memgraph::query::Symbol sink_sym = context.symbol_table.CreateSymbol("sink", true); + memgraph::query::Symbol edges_sym = context.symbol_table.CreateSymbol("edges", true); + memgraph::query::Symbol inner_node_sym = context.symbol_table.CreateSymbol("inner_node", true); + memgraph::query::Symbol inner_edge_sym = context.symbol_table.CreateSymbol("inner_edge", true); + memgraph::query::Identifier *blocked = IDENT("blocked")->MapTo(blocked_sym); + memgraph::query::Identifier *inner_node = IDENT("inner_node")->MapTo(inner_node_sym); + memgraph::query::Identifier *inner_edge = IDENT("inner_edge")->MapTo(inner_edge_sym); - std::tie(vertices, edges) = db->BuildGraph(&dba, kVertexLocations, kEdges); + std::vector<memgraph::query::VertexAccessor> vertices; + std::vector<memgraph::query::EdgeAccessor> edges; - dba.AdvanceCommand(); + std::tie(vertices, edges) = db->BuildGraph(&dba, kVertexLocations, kEdges); - std::shared_ptr<memgraph::query::plan::LogicalOperator> input_op; + dba.AdvanceCommand(); - memgraph::query::Expression *filter_expr; + std::shared_ptr<memgraph::query::plan::LogicalOperator> input_op; - // First build a filter lambda and an operator yielding blocked entities. - switch (filter_lambda_type) { - case FilterLambdaType::NONE: - // No filter lambda, nothing is ever blocked. - input_op = std::make_shared<Yield>( - nullptr, std::vector<memgraph::query::Symbol>{blocked_sym}, - std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue()}}); - filter_expr = nullptr; - break; - case FilterLambdaType::USE_FRAME: - // We block each entity in the graph and run BFS. - input_op = YieldEntities(&dba, vertices, edges, blocked_sym, nullptr); - filter_expr = AND(NEQ(inner_node, blocked), NEQ(inner_edge, blocked)); - break; - case FilterLambdaType::USE_FRAME_NULL: - // We block each entity in the graph and run BFS. - input_op = YieldEntities(&dba, vertices, edges, blocked_sym, nullptr); - filter_expr = IF(AND(NEQ(inner_node, blocked), NEQ(inner_edge, blocked)), LITERAL(true), - LITERAL(memgraph::storage::PropertyValue())); - break; - case FilterLambdaType::USE_CTX: - // We only block vertex #5 and run BFS. - input_op = std::make_shared<Yield>( - nullptr, std::vector<memgraph::query::Symbol>{blocked_sym}, - std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue(vertices[5])}}); - filter_expr = NEQ(PROPERTY_LOOKUP(inner_node, PROPERTY_PAIR("id")), PARAMETER_LOOKUP(0)); - context.evaluation_context.parameters.Add(0, memgraph::storage::PropertyValue(5)); - break; - case FilterLambdaType::ERROR: - // Evaluate to 42 for vertex #5 which is on worker 1. - filter_expr = IF(EQ(PROPERTY_LOOKUP(inner_node, PROPERTY_PAIR("id")), LITERAL(5)), LITERAL(42), LITERAL(true)); - } + memgraph::query::Expression *filter_expr; - // We run BFS once from each vertex for each blocked entity. - input_op = YieldVertices(&dba, vertices, source_sym, input_op); - - // If the sink is known, we run BFS for all posible combinations of source, - // sink and blocked entity. - if (known_sink) { - input_op = YieldVertices(&dba, vertices, sink_sym, input_op); - } - - std::vector<memgraph::storage::EdgeTypeId> storage_edge_types; - for (const auto &t : edge_types) { - storage_edge_types.push_back(dba.NameToEdgeType(t)); - } - - input_op = db->MakeBfsOperator(source_sym, sink_sym, edges_sym, direction, storage_edge_types, input_op, known_sink, - lower_bound == -1 ? nullptr : LITERAL(lower_bound), - upper_bound == -1 ? nullptr : LITERAL(upper_bound), - memgraph::query::plan::ExpansionLambda{inner_edge_sym, inner_node_sym, filter_expr}); - - context.evaluation_context.properties = memgraph::query::NamesToProperties(storage.properties_, &dba); - context.evaluation_context.labels = memgraph::query::NamesToLabels(storage.labels_, &dba); - std::vector<std::vector<memgraph::query::TypedValue>> results; - - // An exception should be thrown on one of the pulls. - if (filter_lambda_type == FilterLambdaType::ERROR) { - EXPECT_THROW(PullResults(input_op.get(), &context, - std::vector<memgraph::query::Symbol>{source_sym, sink_sym, edges_sym, blocked_sym}), - memgraph::query::QueryRuntimeException); - return; - } - - results = PullResults(input_op.get(), &context, - std::vector<memgraph::query::Symbol>{source_sym, sink_sym, edges_sym, blocked_sym}); - - // Group results based on blocked entity and compare them to results - // obtained by running Floyd-Warshall. - for (size_t i = 0; i < results.size();) { - int j = i; - auto blocked = results[j][3]; - while (j < results.size() && memgraph::query::TypedValue::BoolEqual{}(results[j][3], blocked)) ++j; - - SCOPED_TRACE(fmt::format("blocked entity = {}", ToString(blocked, dba))); - - // When an edge is blocked, it is blocked in both directions so we remove - // it before modifying edge list to account for direction and edge types; - auto edges = kEdges; - if (blocked.IsEdge()) { - int from = GetProp(blocked.ValueEdge(), "from", &dba).ValueInt(); - int to = GetProp(blocked.ValueEdge(), "to", &dba).ValueInt(); - edges.erase(std::remove_if(edges.begin(), edges.end(), - [from, to](const auto &e) { return std::get<0>(e) == from && std::get<1>(e) == to; }), - edges.end()); + // First build a filter lambda and an operator yielding blocked entities. + switch (filter_lambda_type) { + case FilterLambdaType::NONE: + // No filter lambda, nothing is ever blocked. + input_op = std::make_shared<Yield>( + nullptr, std::vector<memgraph::query::Symbol>{blocked_sym}, + std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue()}}); + filter_expr = nullptr; + break; + case FilterLambdaType::USE_FRAME: + // We block each entity in the graph and run BFS. + input_op = YieldEntities(&dba, vertices, edges, blocked_sym, nullptr); + filter_expr = AND(NEQ(inner_node, blocked), NEQ(inner_edge, blocked)); + break; + case FilterLambdaType::USE_FRAME_NULL: + // We block each entity in the graph and run BFS. + input_op = YieldEntities(&dba, vertices, edges, blocked_sym, nullptr); + filter_expr = IF(AND(NEQ(inner_node, blocked), NEQ(inner_edge, blocked)), LITERAL(true), + LITERAL(memgraph::storage::PropertyValue())); + break; + case FilterLambdaType::USE_CTX: + // We only block vertex #5 and run BFS. + input_op = std::make_shared<Yield>( + nullptr, std::vector<memgraph::query::Symbol>{blocked_sym}, + std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue(vertices[5])}}); + filter_expr = NEQ(PROPERTY_LOOKUP(dba, inner_node, PROPERTY_PAIR(dba, "id")), PARAMETER_LOOKUP(0)); + context.evaluation_context.parameters.Add(0, memgraph::storage::PropertyValue(5)); + break; + case FilterLambdaType::ERROR: + // Evaluate to 42 for vertex #5 which is on worker 1. + filter_expr = + IF(EQ(PROPERTY_LOOKUP(dba, inner_node, PROPERTY_PAIR(dba, "id")), LITERAL(5)), LITERAL(42), LITERAL(true)); } - // Now add edges in opposite direction if necessary. - auto edges_blocked = GetEdgeList(edges, direction, edge_types); + // We run BFS once from each vertex for each blocked entity. + input_op = YieldVertices(&dba, vertices, source_sym, input_op); - // When a vertex is blocked, we remove all edges that lead into it. - if (blocked.IsVertex()) { - int id = GetProp(blocked.ValueVertex(), "id", &dba).ValueInt(); - edges_blocked.erase( - std::remove_if(edges_blocked.begin(), edges_blocked.end(), [id](const auto &e) { return e.second == id; }), - edges_blocked.end()); + // If the sink is known, we run BFS for all posible combinations of source, + // sink and blocked entity. + if (known_sink) { + input_op = YieldVertices(&dba, vertices, sink_sym, input_op); } - auto correct_with_bounds = FloydWarshall(kVertexCount, edges_blocked); + std::vector<memgraph::storage::EdgeTypeId> storage_edge_types; + for (const auto &t : edge_types) { + storage_edge_types.push_back(dba.NameToEdgeType(t)); + } - if (lower_bound == -1) lower_bound = 0; - if (upper_bound == -1) upper_bound = kVertexCount; + input_op = db->MakeBfsOperator(source_sym, sink_sym, edges_sym, direction, storage_edge_types, input_op, known_sink, + lower_bound == -1 ? nullptr : LITERAL(lower_bound), + upper_bound == -1 ? nullptr : LITERAL(upper_bound), + memgraph::query::plan::ExpansionLambda{inner_edge_sym, inner_node_sym, filter_expr}); - // Remove paths whose length doesn't satisfy given length bounds. - for (int a = 0; a < kVertexCount; ++a) { - for (int b = 0; b < kVertexCount; ++b) { - if (a != b && (correct_with_bounds[a][b] < lower_bound || correct_with_bounds[a][b] > upper_bound)) - correct_with_bounds[a][b] = -1; + context.evaluation_context.properties = memgraph::query::NamesToProperties(storage.properties_, &dba); + context.evaluation_context.labels = memgraph::query::NamesToLabels(storage.labels_, &dba); + std::vector<std::vector<memgraph::query::TypedValue>> results; + + // An exception should be thrown on one of the pulls. + if (filter_lambda_type == FilterLambdaType::ERROR) { + EXPECT_THROW(PullResults(input_op.get(), &context, + std::vector<memgraph::query::Symbol>{source_sym, sink_sym, edges_sym, blocked_sym}), + memgraph::query::QueryRuntimeException); + return; + } + + results = PullResults(input_op.get(), &context, + std::vector<memgraph::query::Symbol>{source_sym, sink_sym, edges_sym, blocked_sym}); + + // Group results based on blocked entity and compare them to results + // obtained by running Floyd-Warshall. + for (size_t i = 0; i < results.size();) { + int j = i; + auto blocked = results[j][3]; + while (j < results.size() && memgraph::query::TypedValue::BoolEqual{}(results[j][3], blocked)) ++j; + + SCOPED_TRACE(fmt::format("blocked entity = {}", ToString(blocked, dba))); + + // When an edge is blocked, it is blocked in both directions so we remove + // it before modifying edge list to account for direction and edge types; + auto edges = kEdges; + if (blocked.IsEdge()) { + int from = GetProp(blocked.ValueEdge(), "from", &dba).ValueInt(); + int to = GetProp(blocked.ValueEdge(), "to", &dba).ValueInt(); + edges.erase( + std::remove_if(edges.begin(), edges.end(), + [from, to](const auto &e) { return std::get<0>(e) == from && std::get<1>(e) == to; }), + edges.end()); } + + // Now add edges in opposite direction if necessary. + auto edges_blocked = GetEdgeList(edges, direction, edge_types); + + // When a vertex is blocked, we remove all edges that lead into it. + if (blocked.IsVertex()) { + int id = GetProp(blocked.ValueVertex(), "id", &dba).ValueInt(); + edges_blocked.erase( + std::remove_if(edges_blocked.begin(), edges_blocked.end(), [id](const auto &e) { return e.second == id; }), + edges_blocked.end()); + } + + auto correct_with_bounds = FloydWarshall(kVertexCount, edges_blocked); + + if (lower_bound == -1) lower_bound = 0; + if (upper_bound == -1) upper_bound = kVertexCount; + + // Remove paths whose length doesn't satisfy given length bounds. + for (int a = 0; a < kVertexCount; ++a) { + for (int b = 0; b < kVertexCount; ++b) { + if (a != b && (correct_with_bounds[a][b] < lower_bound || correct_with_bounds[a][b] > upper_bound)) + correct_with_bounds[a][b] = -1; + } + } + + int num_results = 0; + for (int a = 0; a < kVertexCount; ++a) + for (int b = 0; b < kVertexCount; ++b) + if (a != b && correct_with_bounds[a][b] != -1) { + ++num_results; + } + // There should be exactly 1 successful pull for each existing path. + EXPECT_EQ(j - i, num_results); + + auto distances = CheckPathsAndExtractDistances( + &dba, edges_blocked, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin() + i, results.begin() + j)); + + // The distances should also match. + EXPECT_EQ(distances, correct_with_bounds); + + i = j; } - int num_results = 0; - for (int a = 0; a < kVertexCount; ++a) - for (int b = 0; b < kVertexCount; ++b) - if (a != b && correct_with_bounds[a][b] != -1) { - ++num_results; - } - // There should be exactly 1 successful pull for each existing path. - EXPECT_EQ(j - i, num_results); - - auto distances = CheckPathsAndExtractDistances( - &dba, edges_blocked, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin() + i, results.begin() + j)); - - // The distances should also match. - EXPECT_EQ(distances, correct_with_bounds); - - i = j; + dba.Abort(); } - dba.Abort(); -} - #ifdef MG_ENTERPRISE -void BfsTestWithFineGrainedFiltering(Database *db, int lower_bound, int upper_bound, - memgraph::query::EdgeAtom::Direction direction, - std::vector<std::string> edge_types, bool known_sink, - FineGrainedTestType fine_grained_test_type) { - auto storage_dba = db->Access(); - memgraph::query::DbAccessor db_accessor(&storage_dba); - memgraph::query::AstStorage storage; - memgraph::query::ExecutionContext context{&db_accessor}; - memgraph::query::Symbol blocked_symbol = context.symbol_table.CreateSymbol("blocked", true); - memgraph::query::Symbol source_symbol = context.symbol_table.CreateSymbol("source", true); - memgraph::query::Symbol sink_symbol = context.symbol_table.CreateSymbol("sink", true); - memgraph::query::Symbol edges_symbol = context.symbol_table.CreateSymbol("edges", true); - memgraph::query::Symbol inner_node_symbol = context.symbol_table.CreateSymbol("inner_node", true); - memgraph::query::Symbol inner_edge_symbol = context.symbol_table.CreateSymbol("inner_edge", true); + void BfsTestWithFineGrainedFiltering(Database *db, int lower_bound, int upper_bound, + memgraph::query::EdgeAtom::Direction direction, + std::vector<std::string> edge_types, bool known_sink, + FineGrainedTestType fine_grained_test_type) { + auto storage_dba = db->Access(); + memgraph::query::DbAccessor db_accessor(storage_dba.get()); + memgraph::query::ExecutionContext context{&db_accessor}; + memgraph::query::Symbol blocked_symbol = context.symbol_table.CreateSymbol("blocked", true); + memgraph::query::Symbol source_symbol = context.symbol_table.CreateSymbol("source", true); + memgraph::query::Symbol sink_symbol = context.symbol_table.CreateSymbol("sink", true); + memgraph::query::Symbol edges_symbol = context.symbol_table.CreateSymbol("edges", true); + memgraph::query::Symbol inner_node_symbol = context.symbol_table.CreateSymbol("inner_node", true); + memgraph::query::Symbol inner_edge_symbol = context.symbol_table.CreateSymbol("inner_edge", true); - std::vector<memgraph::query::VertexAccessor> vertices; - std::vector<memgraph::query::EdgeAccessor> edges; + std::vector<memgraph::query::VertexAccessor> vertices; + std::vector<memgraph::query::EdgeAccessor> edges; - std::tie(vertices, edges) = db->BuildGraph(&db_accessor, kVertexLocations, kEdges); + std::tie(vertices, edges) = db->BuildGraph(&db_accessor, kVertexLocations, kEdges); - db_accessor.AdvanceCommand(); + db_accessor.AdvanceCommand(); - std::shared_ptr<memgraph::query::plan::LogicalOperator> input_operator; + std::shared_ptr<memgraph::query::plan::LogicalOperator> input_operator; - memgraph::query::Expression *filter_expr = nullptr; + memgraph::query::Expression *filter_expr = nullptr; - input_operator = - std::make_shared<Yield>(nullptr, std::vector<memgraph::query::Symbol>{blocked_symbol}, - std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue()}}); + input_operator = + std::make_shared<Yield>(nullptr, std::vector<memgraph::query::Symbol>{blocked_symbol}, + std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue()}}); - memgraph::auth::User user{"test"}; - std::vector<std::pair<int, int>> edges_in_result; - switch (fine_grained_test_type) { - case FineGrainedTestType::ALL_GRANTED: - user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("*", - memgraph::auth::FineGrainedPermission::READ); - edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); - break; - case FineGrainedTestType::ALL_DENIED: - break; - case FineGrainedTestType::EDGE_TYPE_A_DENIED: - user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("b", - memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("a", - memgraph::auth::FineGrainedPermission::NOTHING); + memgraph::auth::User user{"test"}; + std::vector<std::pair<int, int>> edges_in_result; + switch (fine_grained_test_type) { + case FineGrainedTestType::ALL_GRANTED: + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", + memgraph::auth::FineGrainedPermission::READ); + edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); + break; + case FineGrainedTestType::ALL_DENIED: + break; + case FineGrainedTestType::EDGE_TYPE_A_DENIED: + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("b", + memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant( + "a", memgraph::auth::FineGrainedPermission::NOTHING); - edges_in_result = GetEdgeList(kEdges, direction, {"b"}); - break; - case FineGrainedTestType::EDGE_TYPE_B_DENIED: - user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("a", - memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("b", - memgraph::auth::FineGrainedPermission::NOTHING); + edges_in_result = GetEdgeList(kEdges, direction, {"b"}); + break; + case FineGrainedTestType::EDGE_TYPE_B_DENIED: + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("a", + memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant( + "b", memgraph::auth::FineGrainedPermission::NOTHING); - edges_in_result = GetEdgeList(kEdges, direction, {"a"}); - break; - case FineGrainedTestType::LABEL_0_DENIED: - user.fine_grained_access_handler().edge_type_permissions().Grant("*", - memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("3", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("0", memgraph::auth::FineGrainedPermission::NOTHING); + edges_in_result = GetEdgeList(kEdges, direction, {"a"}); + break; + case FineGrainedTestType::LABEL_0_DENIED: + user.fine_grained_access_handler().edge_type_permissions().Grant("*", + memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("3", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("0", + memgraph::auth::FineGrainedPermission::NOTHING); - edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); - edges_in_result.erase( - std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 0; }), - edges_in_result.end()); - break; - case FineGrainedTestType::LABEL_3_DENIED: - user.fine_grained_access_handler().edge_type_permissions().Grant("*", - memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("0", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().label_permissions().Grant("3", memgraph::auth::FineGrainedPermission::NOTHING); + edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); + edges_in_result.erase( + std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 0; }), + edges_in_result.end()); + break; + case FineGrainedTestType::LABEL_3_DENIED: + user.fine_grained_access_handler().edge_type_permissions().Grant("*", + memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("0", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("3", + memgraph::auth::FineGrainedPermission::NOTHING); - edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); - edges_in_result.erase( - std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 3; }), - edges_in_result.end()); - break; + edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"}); + edges_in_result.erase( + std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 3; }), + edges_in_result.end()); + break; + } + + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &db_accessor}; + context.auth_checker = std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(auth_checker)); + // We run BFS once from each vertex for each blocked entity. + input_operator = YieldVertices(&db_accessor, vertices, source_symbol, input_operator); + + // If the sink is known, we run BFS for all posible combinations of source, + // sink and blocked entity. + if (known_sink) { + input_operator = YieldVertices(&db_accessor, vertices, sink_symbol, input_operator); + } + + std::vector<memgraph::storage::EdgeTypeId> storage_edge_types; + for (const auto &t : edge_types) { + storage_edge_types.push_back(db_accessor.NameToEdgeType(t)); + } + + input_operator = db->MakeBfsOperator( + source_symbol, sink_symbol, edges_symbol, direction, storage_edge_types, input_operator, known_sink, + lower_bound == -1 ? nullptr : LITERAL(lower_bound), upper_bound == -1 ? nullptr : LITERAL(upper_bound), + memgraph::query::plan::ExpansionLambda{inner_edge_symbol, inner_node_symbol, filter_expr}); + + context.evaluation_context.properties = memgraph::query::NamesToProperties(storage.properties_, &db_accessor); + context.evaluation_context.labels = memgraph::query::NamesToLabels(storage.labels_, &db_accessor); + std::vector<std::vector<memgraph::query::TypedValue>> results; + + results = + PullResults(input_operator.get(), &context, + std::vector<memgraph::query::Symbol>{source_symbol, sink_symbol, edges_symbol, blocked_symbol}); + + switch (fine_grained_test_type) { + case FineGrainedTestType::ALL_GRANTED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::OUT: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + } + break; + case FineGrainedTestType::ALL_DENIED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + EXPECT_EQ(results.size(), 0); + break; + case memgraph::query::EdgeAtom::Direction::OUT: + EXPECT_EQ(results.size(), 0); + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + EXPECT_EQ(results.size(), 0); + break; + } + break; + case FineGrainedTestType::EDGE_TYPE_A_DENIED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::OUT: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + } + break; + case FineGrainedTestType::EDGE_TYPE_B_DENIED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::OUT: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + break; + } + break; + case FineGrainedTestType::LABEL_0_DENIED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + case memgraph::query::EdgeAtom::Direction::OUT: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + } + break; + case FineGrainedTestType::LABEL_3_DENIED: + switch (direction) { + case memgraph::query::EdgeAtom::Direction::IN: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + case memgraph::query::EdgeAtom::Direction::OUT: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + case memgraph::query::EdgeAtom::Direction::BOTH: + if (known_sink) { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } else { + CheckPathsAndExtractDistances( + &db_accessor, edges_in_result, + std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); + } + break; + } + break; + } + + db_accessor.Abort(); } - - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &db_accessor}; - context.auth_checker = std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(auth_checker)); - // We run BFS once from each vertex for each blocked entity. - input_operator = YieldVertices(&db_accessor, vertices, source_symbol, input_operator); - - // If the sink is known, we run BFS for all posible combinations of source, - // sink and blocked entity. - if (known_sink) { - input_operator = YieldVertices(&db_accessor, vertices, sink_symbol, input_operator); - } - - std::vector<memgraph::storage::EdgeTypeId> storage_edge_types; - for (const auto &t : edge_types) { - storage_edge_types.push_back(db_accessor.NameToEdgeType(t)); - } - - input_operator = db->MakeBfsOperator( - source_symbol, sink_symbol, edges_symbol, direction, storage_edge_types, input_operator, known_sink, - lower_bound == -1 ? nullptr : LITERAL(lower_bound), upper_bound == -1 ? nullptr : LITERAL(upper_bound), - memgraph::query::plan::ExpansionLambda{inner_edge_symbol, inner_node_symbol, filter_expr}); - - context.evaluation_context.properties = memgraph::query::NamesToProperties(storage.properties_, &db_accessor); - context.evaluation_context.labels = memgraph::query::NamesToLabels(storage.labels_, &db_accessor); - std::vector<std::vector<memgraph::query::TypedValue>> results; - - results = PullResults(input_operator.get(), &context, - std::vector<memgraph::query::Symbol>{source_symbol, sink_symbol, edges_symbol, blocked_symbol}); - - switch (fine_grained_test_type) { - case FineGrainedTestType::ALL_GRANTED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::OUT: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - } - break; - case FineGrainedTestType::ALL_DENIED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - EXPECT_EQ(results.size(), 0); - break; - case memgraph::query::EdgeAtom::Direction::OUT: - EXPECT_EQ(results.size(), 0); - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - EXPECT_EQ(results.size(), 0); - break; - } - break; - case FineGrainedTestType::EDGE_TYPE_A_DENIED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::OUT: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - } - break; - case FineGrainedTestType::EDGE_TYPE_B_DENIED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::OUT: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - break; - } - break; - case FineGrainedTestType::LABEL_0_DENIED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - case memgraph::query::EdgeAtom::Direction::OUT: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - } - break; - case FineGrainedTestType::LABEL_3_DENIED: - switch (direction) { - case memgraph::query::EdgeAtom::Direction::IN: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - case memgraph::query::EdgeAtom::Direction::OUT: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - case memgraph::query::EdgeAtom::Direction::BOTH: - if (known_sink) { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } else { - CheckPathsAndExtractDistances( - &db_accessor, edges_in_result, - std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin())); - } - break; - } - break; - } - - db_accessor.Abort(); -} #endif + + protected: + memgraph::query::AstStorage storage; +}; diff --git a/tests/unit/bfs_fine_grained.cpp b/tests/unit/bfs_fine_grained.cpp index e628a4c6b..235431804 100644 --- a/tests/unit/bfs_fine_grained.cpp +++ b/tests/unit/bfs_fine_grained.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,16 +18,31 @@ #include <gtest/internal/gtest-param-util-generated.h> #include "auth/models.hpp" +#include "disk_test_utils.hpp" #include "license/license.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; +template <typename StorageType> class VertexDb : public Database { public: - VertexDb() : db_() {} + const std::string testSuite = "bfs_fine_grained"; - memgraph::storage::Storage::Accessor Access() override { return db_.Access(); } + VertexDb() { + config_ = disk_test_utils::GenerateOnDiskConfig(testSuite); + db_ = std::make_unique<StorageType>(config_); + } + + ~VertexDb() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); } std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym, EdgeAtom::Direction direction, @@ -71,25 +86,27 @@ class VertexDb : public Database { } protected: - memgraph::storage::Storage db_; + memgraph::storage::Config config_; + std::unique_ptr<memgraph::storage::Storage> db_; }; #ifdef MG_ENTERPRISE -class FineGrainedBfsTest +class FineGrainedBfsTestInMemory : public ::testing::TestWithParam< std::tuple<int, int, EdgeAtom::Direction, std::vector<std::string>, bool, FineGrainedTestType>> { public: + using StorageType = memgraph::storage::InMemoryStorage; static void SetUpTestCase() { memgraph::license::global_license_checker.EnableTesting(); - db_ = std::make_unique<VertexDb>(); + db_ = std::make_unique<VertexDb<StorageType>>(); } static void TearDownTestCase() { db_ = nullptr; } protected: - static std::unique_ptr<VertexDb> db_; + static std::unique_ptr<VertexDb<StorageType>> db_; }; -TEST_P(FineGrainedBfsTest, All) { +TEST_P(FineGrainedBfsTestInMemory, All) { int lower_bound; int upper_bound; EdgeAtom::Direction direction; @@ -99,13 +116,54 @@ TEST_P(FineGrainedBfsTest, All) { std::tie(lower_bound, upper_bound, direction, edge_types, known_sink, fine_grained_test_type) = GetParam(); - BfsTestWithFineGrainedFiltering(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, - fine_grained_test_type); + this->db_->BfsTestWithFineGrainedFiltering(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, + fine_grained_test_type); } -std::unique_ptr<VertexDb> FineGrainedBfsTest::db_{nullptr}; +std::unique_ptr<VertexDb<FineGrainedBfsTestInMemory::StorageType>> FineGrainedBfsTestInMemory::db_{nullptr}; + INSTANTIATE_TEST_CASE_P( - FineGrained, FineGrainedBfsTest, + FineGrained, FineGrainedBfsTestInMemory, + testing::Combine(testing::Values(3), testing::Values(-1), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}), testing::Bool(), + testing::Values(FineGrainedTestType::ALL_GRANTED, FineGrainedTestType::ALL_DENIED, + FineGrainedTestType::EDGE_TYPE_A_DENIED, FineGrainedTestType::EDGE_TYPE_B_DENIED, + FineGrainedTestType::LABEL_0_DENIED, FineGrainedTestType::LABEL_3_DENIED))); + +class FineGrainedBfsTestOnDisk + : public ::testing::TestWithParam< + std::tuple<int, int, EdgeAtom::Direction, std::vector<std::string>, bool, FineGrainedTestType>> { + public: + using StorageType = memgraph::storage::DiskStorage; + static void SetUpTestCase() { + memgraph::license::global_license_checker.EnableTesting(); + db_ = std::make_unique<VertexDb<StorageType>>(); + } + static void TearDownTestCase() { db_ = nullptr; } + + protected: + static std::unique_ptr<VertexDb<StorageType>> db_; +}; + +TEST_P(FineGrainedBfsTestOnDisk, All) { + int lower_bound; + int upper_bound; + EdgeAtom::Direction direction; + std::vector<std::string> edge_types; + bool known_sink; + FineGrainedTestType fine_grained_test_type; + + std::tie(lower_bound, upper_bound, direction, edge_types, known_sink, fine_grained_test_type) = GetParam(); + + this->db_->BfsTestWithFineGrainedFiltering(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, + fine_grained_test_type); +} + +std::unique_ptr<VertexDb<FineGrainedBfsTestOnDisk::StorageType>> FineGrainedBfsTestOnDisk::db_{nullptr}; + +INSTANTIATE_TEST_CASE_P( + FineGrained, FineGrainedBfsTestOnDisk, testing::Combine(testing::Values(3), testing::Values(-1), testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), testing::Values(std::vector<std::string>{}), testing::Bool(), diff --git a/tests/unit/bfs_single_node.cpp b/tests/unit/bfs_single_node.cpp index 93002eef5..37aae0491 100644 --- a/tests/unit/bfs_single_node.cpp +++ b/tests/unit/bfs_single_node.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,14 +11,27 @@ #include "bfs_common.hpp" +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" + using namespace memgraph::query; using namespace memgraph::query::plan; +template <typename StorageType> class SingleNodeDb : public Database { public: - SingleNodeDb() : db_() {} + const std::string testSuite = "bfs_single_node"; - memgraph::storage::Storage::Accessor Access() override { return db_.Access(); } + SingleNodeDb() : config_(disk_test_utils::GenerateOnDiskConfig(testSuite)), db_(new StorageType(config_)) {} + + ~SingleNodeDb() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); } std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym, EdgeAtom::Direction direction, @@ -61,21 +74,23 @@ class SingleNodeDb : public Database { } protected: - memgraph::storage::Storage db_; + memgraph::storage::Config config_; + std::unique_ptr<memgraph::storage::Storage> db_; }; -class SingleNodeBfsTest +class SingleNodeBfsTestInMemory : public ::testing::TestWithParam< std::tuple<int, int, EdgeAtom::Direction, std::vector<std::string>, bool, FilterLambdaType>> { public: - static void SetUpTestCase() { db_ = std::make_unique<SingleNodeDb>(); } + using StorageType = memgraph::storage::InMemoryStorage; + static void SetUpTestCase() { db_ = std::make_unique<SingleNodeDb<StorageType>>(); } static void TearDownTestCase() { db_ = nullptr; } protected: - static std::unique_ptr<SingleNodeDb> db_; + static std::unique_ptr<SingleNodeDb<StorageType>> db_; }; -TEST_P(SingleNodeBfsTest, All) { +TEST_P(SingleNodeBfsTestInMemory, All) { int lower_bound; int upper_bound; EdgeAtom::Direction direction; @@ -83,12 +98,12 @@ TEST_P(SingleNodeBfsTest, All) { bool known_sink; FilterLambdaType filter_lambda_type; std::tie(lower_bound, upper_bound, direction, edge_types, known_sink, filter_lambda_type) = GetParam(); - BfsTest(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, filter_lambda_type); + this->db_->BfsTest(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, filter_lambda_type); } -std::unique_ptr<SingleNodeDb> SingleNodeBfsTest::db_{nullptr}; +std::unique_ptr<SingleNodeDb<SingleNodeBfsTestInMemory::StorageType>> SingleNodeBfsTestInMemory::db_{nullptr}; -INSTANTIATE_TEST_CASE_P(DirectionAndExpansionDepth, SingleNodeBfsTest, +INSTANTIATE_TEST_CASE_P(DirectionAndExpansionDepth, SingleNodeBfsTestInMemory, testing::Combine(testing::Range(-1, kVertexCount), testing::Range(-1, kVertexCount), testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), @@ -96,14 +111,63 @@ INSTANTIATE_TEST_CASE_P(DirectionAndExpansionDepth, SingleNodeBfsTest, testing::Values(FilterLambdaType::NONE))); INSTANTIATE_TEST_CASE_P( - EdgeType, SingleNodeBfsTest, + EdgeType, SingleNodeBfsTestInMemory, testing::Combine(testing::Values(-1), testing::Values(-1), testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), testing::Values(std::vector<std::string>{}, std::vector<std::string>{"a"}, std::vector<std::string>{"b"}, std::vector<std::string>{"a", "b"}), testing::Bool(), testing::Values(FilterLambdaType::NONE))); -INSTANTIATE_TEST_CASE_P(FilterLambda, SingleNodeBfsTest, +INSTANTIATE_TEST_CASE_P(FilterLambda, SingleNodeBfsTestInMemory, + testing::Combine(testing::Values(-1), testing::Values(-1), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, + EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}), testing::Bool(), + testing::Values(FilterLambdaType::NONE, FilterLambdaType::USE_FRAME, + FilterLambdaType::USE_FRAME_NULL, FilterLambdaType::USE_CTX, + FilterLambdaType::ERROR))); + +class SingleNodeBfsTestOnDisk + : public ::testing::TestWithParam< + std::tuple<int, int, EdgeAtom::Direction, std::vector<std::string>, bool, FilterLambdaType>> { + public: + using StorageType = memgraph::storage::DiskStorage; + static void SetUpTestCase() { db_ = std::make_unique<SingleNodeDb<StorageType>>(); } + static void TearDownTestCase() { db_ = nullptr; } + + protected: + static std::unique_ptr<SingleNodeDb<StorageType>> db_; +}; + +TEST_P(SingleNodeBfsTestOnDisk, All) { + int lower_bound; + int upper_bound; + EdgeAtom::Direction direction; + std::vector<std::string> edge_types; + bool known_sink; + FilterLambdaType filter_lambda_type; + std::tie(lower_bound, upper_bound, direction, edge_types, known_sink, filter_lambda_type) = GetParam(); + this->db_->BfsTest(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink, filter_lambda_type); +} + +std::unique_ptr<SingleNodeDb<SingleNodeBfsTestOnDisk::StorageType>> SingleNodeBfsTestOnDisk::db_{nullptr}; + +INSTANTIATE_TEST_CASE_P(DirectionAndExpansionDepth, SingleNodeBfsTestOnDisk, + testing::Combine(testing::Range(-1, kVertexCount), testing::Range(-1, kVertexCount), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, + EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}), testing::Bool(), + testing::Values(FilterLambdaType::NONE))); + +INSTANTIATE_TEST_CASE_P( + EdgeType, SingleNodeBfsTestOnDisk, + testing::Combine(testing::Values(-1), testing::Values(-1), + testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), + testing::Values(std::vector<std::string>{}, std::vector<std::string>{"a"}, + std::vector<std::string>{"b"}, std::vector<std::string>{"a", "b"}), + testing::Bool(), testing::Values(FilterLambdaType::NONE))); + +INSTANTIATE_TEST_CASE_P(FilterLambda, SingleNodeBfsTestOnDisk, testing::Combine(testing::Values(-1), testing::Values(-1), testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH), diff --git a/tests/unit/bolt_encoder.cpp b/tests/unit/bolt_encoder.cpp index 41b5c60cb..6ffb4b496 100644 --- a/tests/unit/bolt_encoder.cpp +++ b/tests/unit/bolt_encoder.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,12 +11,16 @@ #include <array> #include <bit> +#include <memory> #include "bolt_common.hpp" #include "bolt_testdata.hpp" #include "communication/bolt/v1/codes.hpp" #include "communication/bolt/v1/encoder/encoder.hpp" +#include "disk_test_utils.hpp" #include "glue/communication.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "utils/temporal.hpp" using memgraph::communication::bolt::Value; @@ -174,40 +178,39 @@ TEST_F(BoltEncoder, Map) { CheckOutput(output, nullptr, 0); } -TEST_F(BoltEncoder, VertexAndEdge) { +void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr<memgraph::storage::Storage> &&db) { output.clear(); // create vertex - memgraph::storage::Storage db; - auto dba = db.Access(); - auto va1 = dba.CreateVertex(); - auto va2 = dba.CreateVertex(); - auto l1 = dba.NameToLabel("label1"); - auto l2 = dba.NameToLabel("label2"); + auto dba = db->Access(); + auto va1 = dba->CreateVertex(); + auto va2 = dba->CreateVertex(); + auto l1 = dba->NameToLabel("label1"); + auto l2 = dba->NameToLabel("label2"); ASSERT_TRUE(va1.AddLabel(l1).HasValue()); ASSERT_TRUE(va1.AddLabel(l2).HasValue()); - auto p1 = dba.NameToProperty("prop1"); - auto p2 = dba.NameToProperty("prop2"); + auto p1 = dba->NameToProperty("prop1"); + auto p2 = dba->NameToProperty("prop2"); memgraph::storage::PropertyValue pv1(12), pv2(200); ASSERT_TRUE(va1.SetProperty(p1, pv1).HasValue()); ASSERT_TRUE(va1.SetProperty(p2, pv2).HasValue()); // create edge - auto et = dba.NameToEdgeType("edgetype"); - auto ea = dba.CreateEdge(&va1, &va2, et); - auto p3 = dba.NameToProperty("prop3"); - auto p4 = dba.NameToProperty("prop4"); + auto et = dba->NameToEdgeType("edgetype"); + auto ea = dba->CreateEdge(&va1, &va2, et).GetValue(); + auto p3 = dba->NameToProperty("prop3"); + auto p4 = dba->NameToProperty("prop4"); memgraph::storage::PropertyValue pv3(42), pv4(1234); - ASSERT_TRUE(ea->SetProperty(p3, pv3).HasValue()); - ASSERT_TRUE(ea->SetProperty(p4, pv4).HasValue()); + ASSERT_TRUE(ea.SetProperty(p3, pv3).HasValue()); + ASSERT_TRUE(ea.SetProperty(p4, pv4).HasValue()); // check everything std::vector<Value> vals; - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), db, + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), *db, memgraph::storage::View::NEW)); - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), db, + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), *db, memgraph::storage::View::NEW)); - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(*ea)), db, + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), *db, memgraph::storage::View::NEW)); bolt_encoder.MessageRecord(vals); @@ -219,12 +222,27 @@ TEST_F(BoltEncoder, VertexAndEdge) { CheckOutput(output, vertexedge_encoded + 6, 34, false); CheckInt(output, va2.Gid().AsInt()); CheckOutput(output, vertexedge_encoded + 41, 4, false); - CheckInt(output, ea->Gid().AsInt()); + CheckInt(output, ea.Gid().AsInt()); CheckInt(output, va1.Gid().AsInt()); CheckInt(output, va2.Gid().AsInt()); CheckOutput(output, vertexedge_encoded + 48, 26); } +TEST_F(BoltEncoder, VertexAndEdgeInMemoryStorage) { + std::unique_ptr<memgraph::storage::Storage> db{new memgraph::storage::InMemoryStorage()}; + TestVertexAndEdgeWithDifferentStorages(std::move(db)); +} + +TEST_F(BoltEncoder, VertexAndEdgeOnDiskStorage) { + const std::string testSuite = "bolt_encoder"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + + std::unique_ptr<memgraph::storage::Storage> db{new memgraph::storage::DiskStorage(config)}; + TestVertexAndEdgeWithDifferentStorages(std::move(db)); + + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + TEST_F(BoltEncoder, BoltV1ExampleMessages) { // this test checks example messages from: http://boltprotocol.org/v1/ diff --git a/tests/unit/clearing_old_disk_data.cpp b/tests/unit/clearing_old_disk_data.cpp new file mode 100644 index 000000000..d27938f50 --- /dev/null +++ b/tests/unit/clearing_old_disk_data.cpp @@ -0,0 +1,181 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <gtest/gtest.h> +#include <rocksdb/options.h> +#include <limits> + +#include "disk_test_utils.hpp" +#include "spdlog/spdlog.h" +#include "storage/v2/disk/rocksdb_storage.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/view.hpp" + +class ClearingOldDiskDataTest : public ::testing::Test { + public: + const std::string testSuite = "clearing_old_disk_data"; + std::unique_ptr<memgraph::storage::DiskStorage> disk_storage = + std::make_unique<memgraph::storage::DiskStorage>(disk_test_utils::GenerateOnDiskConfig(testSuite)); + + void TearDown() override { disk_test_utils::RemoveRocksDbDirs(testSuite); } +}; + +TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexTimestampUpdate) { + auto *tx_db = disk_storage->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); + + auto acc1 = disk_storage->Access(std::nullopt); + auto vertex1 = acc1->CreateVertex(); + auto label1 = acc1->NameToLabel("DiskLabel"); + auto property1 = acc1->NameToProperty("DiskProperty"); + ASSERT_TRUE(vertex1.AddLabel(label1).HasValue()); + ASSERT_TRUE(vertex1.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc1->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = disk_storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); + /// This is the same property as in the first transaction, we just want to test + /// the number of entries inside RocksDB when the timestamp changes + auto property2 = acc2->NameToProperty("DiskProperty"); + ASSERT_TRUE(vertex2.SetProperty(property2, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); +} + +TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexValueUpdate) { + auto *tx_db = disk_storage->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); + + auto acc1 = disk_storage->Access(std::nullopt); + auto vertex1 = acc1->CreateVertex(); + auto label1 = acc1->NameToLabel("DiskLabel"); + auto property1 = acc1->NameToProperty("DiskProperty"); + ASSERT_TRUE(vertex1.AddLabel(label1).HasValue()); + ASSERT_TRUE(vertex1.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc1->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = disk_storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); + /// This is the same property as in the first transaction, we just want to test + /// the number of entries inside RocksDB when the timestamp changes + auto property2 = acc2->NameToProperty("DiskProperty"); + ASSERT_TRUE(vertex2.SetProperty(property2, memgraph::storage::PropertyValue(15)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); +} + +TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexKeyUpdate) { + auto *tx_db = disk_storage->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); + + auto acc1 = disk_storage->Access(std::nullopt); + auto vertex1 = acc1->CreateVertex(); + auto label1 = acc1->NameToLabel("DiskLabel"); + auto property1 = acc1->NameToProperty("DiskProperty"); + ASSERT_TRUE(vertex1.AddLabel(label1).HasValue()); + ASSERT_TRUE(vertex1.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc1->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = disk_storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); + auto label2 = acc2->NameToLabel("DiskLabel2"); + ASSERT_TRUE(vertex2.AddLabel(label2).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); +} + +TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeTimestampUpdate) { + auto *tx_db = disk_storage->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); + + auto acc1 = disk_storage->Access(std::nullopt); + + auto label1 = acc1->NameToLabel("DiskLabel"); + auto property1 = acc1->NameToProperty("DiskProperty"); + auto edge_type = acc1->NameToEdgeType("test"); + + auto from = acc1->CreateVertex(); + auto to = acc1->CreateVertex(); + auto edge = acc1->CreateEdge(&from, &to, edge_type); + MG_ASSERT(edge.HasValue()); + + ASSERT_TRUE(from.AddLabel(label1).HasValue()); + ASSERT_TRUE(to.AddLabel(label1).HasValue()); + ASSERT_TRUE(from.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_TRUE(to.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_TRUE(edge->SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc1->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 3); + + auto acc2 = disk_storage->Access(std::nullopt); + auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value(); + + acc2->PrefetchOutEdges(from_vertex); + auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW); + auto fetched_edge = ret.GetValue()[0]; + + /// This is the same property as in the first transaction, we just want to test + /// the number of entries inside RocksDB when the timestamp changes + auto property2 = acc2->NameToProperty("DiskProperty"); + ASSERT_TRUE(fetched_edge.SetProperty(property2, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 3); +} + +TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeValueUpdate) { + auto *tx_db = disk_storage->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); + + auto acc1 = disk_storage->Access(std::nullopt); + + auto label1 = acc1->NameToLabel("DiskLabel"); + auto property1 = acc1->NameToProperty("DiskProperty"); + auto edge_type = acc1->NameToEdgeType("test"); + + auto from = acc1->CreateVertex(); + auto to = acc1->CreateVertex(); + auto edge = acc1->CreateEdge(&from, &to, edge_type); + MG_ASSERT(edge.HasValue()); + + ASSERT_TRUE(from.AddLabel(label1).HasValue()); + ASSERT_TRUE(to.AddLabel(label1).HasValue()); + ASSERT_TRUE(from.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_TRUE(to.SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_TRUE(edge->SetProperty(property1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc1->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 3); + + auto acc2 = disk_storage->Access(std::nullopt); + auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value(); + + acc2->PrefetchOutEdges(from_vertex); + auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW); + auto fetched_edge = ret.GetValue()[0]; + + auto property2 = acc2->NameToProperty("DiskProperty"); + ASSERT_TRUE(fetched_edge.SetProperty(property2, memgraph::storage::PropertyValue(15)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 3); +} diff --git a/tests/unit/cpp_api.cpp b/tests/unit/cpp_api.cpp index f2e47f03e..95c96426a 100644 --- a/tests/unit/cpp_api.cpp +++ b/tests/unit/cpp_api.cpp @@ -16,13 +16,23 @@ #include <gflags/gflags.h> #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "mgp.hpp" #include "query/procedure/mg_procedure_impl.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/view.hpp" +template <typename StorageType> struct CppApiTestFixture : public ::testing::Test { protected: - virtual void SetUp() { mgp::memory = &memory; } + virtual void SetUp() override { mgp::memory = &memory; } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } mgp_graph CreateGraph(const memgraph::storage::View view = memgraph::storage::View::NEW) { // the execution context can be null as it shouldn't be used in these tests @@ -30,22 +40,28 @@ struct CppApiTestFixture : public ::testing::Test { } memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) { - accessors_.push_back(storage.Access(isolationLevel)); - db_accessors_.emplace_back(&accessors_.back()); + accessors_.push_back(storage->Access(isolationLevel)); + db_accessors_.emplace_back(accessors_.back().get()); return db_accessors_.back(); } - memgraph::storage::Storage storage; + const std::string testSuite = "cpp_api"; + + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> storage{new StorageType(config)}; mgp_memory memory{memgraph::utils::NewDeleteResource()}; private: - std::list<memgraph::storage::Storage::Accessor> accessors_; + std::list<std::unique_ptr<memgraph::storage::Storage::Accessor>> accessors_; std::list<memgraph::query::DbAccessor> db_accessors_; std::unique_ptr<memgraph::query::ExecutionContext> ctx_ = std::make_unique<memgraph::query::ExecutionContext>(); }; -TEST_F(CppApiTestFixture, TestGraph) { - mgp_graph raw_graph = CreateGraph(); +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(CppApiTestFixture, StorageTypes); + +TYPED_TEST(CppApiTestFixture, TestGraph) { + mgp_graph raw_graph = this->CreateGraph(); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); @@ -85,7 +101,7 @@ TEST_F(CppApiTestFixture, TestGraph) { ASSERT_EQ(n_rels, 3); } -TEST_F(CppApiTestFixture, TestId) { +TYPED_TEST(CppApiTestFixture, TestId) { int64_t int_1 = 8; uint64_t int_2 = 8; int64_t int_3 = 7; @@ -116,7 +132,7 @@ TEST_F(CppApiTestFixture, TestId) { ASSERT_NE(id_2, id_4); } -TEST_F(CppApiTestFixture, TestList) { +TYPED_TEST(CppApiTestFixture, TestList) { auto list_1 = mgp::List(); ASSERT_EQ(list_1.Size(), 0); @@ -154,7 +170,7 @@ TEST_F(CppApiTestFixture, TestList) { auto value_y = mgp::Value(mgp::List()); } -TEST_F(CppApiTestFixture, TestMap) { +TYPED_TEST(CppApiTestFixture, TestMap) { auto map_1 = mgp::Map(); std::map<std::string_view, mgp::Value> map_1a; @@ -196,8 +212,8 @@ TEST_F(CppApiTestFixture, TestMap) { auto value_z = value_x; } -TEST_F(CppApiTestFixture, TestNode) { - mgp_graph raw_graph = CreateGraph(); +TYPED_TEST(CppApiTestFixture, TestNode) { + mgp_graph raw_graph = this->CreateGraph(); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); @@ -243,8 +259,8 @@ TEST_F(CppApiTestFixture, TestNode) { auto value_y = mgp::Value(graph.CreateNode()); } -TEST_F(CppApiTestFixture, TestNodeWithNeighbors) { - mgp_graph raw_graph = CreateGraph(); +TYPED_TEST(CppApiTestFixture, TestNodeWithNeighbors) { + mgp_graph raw_graph = this->CreateGraph(); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); @@ -270,8 +286,8 @@ TEST_F(CppApiTestFixture, TestNodeWithNeighbors) { ASSERT_EQ(count_in_relationships, 2); } -TEST_F(CppApiTestFixture, TestRelationship) { - mgp_graph raw_graph = CreateGraph(); +TYPED_TEST(CppApiTestFixture, TestRelationship) { + mgp_graph raw_graph = this->CreateGraph(); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); @@ -297,8 +313,8 @@ TEST_F(CppApiTestFixture, TestRelationship) { auto value_y = mgp::Value(graph.CreateRelationship(node_2, node_1, "edge_type")); } -TEST_F(CppApiTestFixture, TestPath) { - mgp_graph raw_graph = CreateGraph(); +TYPED_TEST(CppApiTestFixture, TestPath) { + mgp_graph raw_graph = this->CreateGraph(); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); @@ -330,7 +346,7 @@ TEST_F(CppApiTestFixture, TestPath) { auto value_y = mgp::Value(mgp::Path(node_0)); } -TEST_F(CppApiTestFixture, TestDate) { +TYPED_TEST(CppApiTestFixture, TestDate) { auto date_1 = mgp::Date("2022-04-09"); auto date_2 = mgp::Date(2022, 4, 9); @@ -357,7 +373,7 @@ TEST_F(CppApiTestFixture, TestDate) { auto value_y = mgp::Value(mgp::Date("2022-04-09")); } -TEST_F(CppApiTestFixture, TestLocalTime) { +TYPED_TEST(CppApiTestFixture, TestLocalTime) { auto lt_1 = mgp::LocalTime("09:15:00"); auto lt_2 = mgp::LocalTime(9, 15, 0, 0, 0); auto lt_3 = mgp::LocalTime::Now(); @@ -385,7 +401,7 @@ TEST_F(CppApiTestFixture, TestLocalTime) { auto value_y = mgp::Value(mgp::LocalTime("09:15:00")); } -TEST_F(CppApiTestFixture, TestLocalDateTime) { +TYPED_TEST(CppApiTestFixture, TestLocalDateTime) { auto ldt_1 = mgp::LocalDateTime("2021-10-05T14:15:00"); auto ldt_2 = mgp::LocalDateTime(2021, 10, 5, 14, 15, 0, 0, 0); @@ -418,7 +434,7 @@ TEST_F(CppApiTestFixture, TestLocalDateTime) { auto value_y = mgp::Value(mgp::LocalDateTime("2021-10-05T14:15:00")); } -TEST_F(CppApiTestFixture, TestDuration) { +TYPED_TEST(CppApiTestFixture, TestDuration) { auto duration_1 = mgp::Duration("PT2M2.33S"); auto duration_2 = mgp::Duration(1465355); auto duration_3 = mgp::Duration(5, 14, 15, 0, 0, 0); @@ -440,8 +456,8 @@ TEST_F(CppApiTestFixture, TestDuration) { auto value_y = mgp::Value(mgp::Duration("PT2M2.33S")); } -TEST_F(CppApiTestFixture, TestNodeProperties) { - mgp_graph raw_graph = CreateGraph(memgraph::storage::View::NEW); +TYPED_TEST(CppApiTestFixture, TestNodeProperties) { + mgp_graph raw_graph = this->CreateGraph(memgraph::storage::View::NEW); auto graph = mgp::Graph(&raw_graph); auto node_1 = graph.CreateNode(); diff --git a/tests/unit/disk_test_utils.hpp b/tests/unit/disk_test_utils.hpp new file mode 100644 index 000000000..259e78909 --- /dev/null +++ b/tests/unit/disk_test_utils.hpp @@ -0,0 +1,49 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <rocksdb/utilities/transaction_db.h> +#include <filesystem> +#include "storage/v2/config.hpp" +#include "storage/v2/disk/storage.hpp" + +namespace disk_test_utils { + +memgraph::storage::Config GenerateOnDiskConfig(const std::string &testName) { + return {.disk = {.main_storage_directory = "rocksdb_" + testName + "_db", + .label_index_directory = "rocksdb_" + testName + "_label_index", + .label_property_index_directory = "rocksdb_" + testName + "_label_property_index", + .unique_constraints_directory = "rocksdb_" + testName + "_unique_constraints", + .name_id_mapper_directory = "rocksdb_" + testName + "_name_id_mapper", + .id_name_mapper_directory = "rocksdb_" + testName + "_id_name_mapper", + .durability_directory = "rocksdb_" + testName + "_durability", + .wal_directory = "rocksdb_" + testName + "_wal"}}; +} + +void RemoveRocksDbDirs(const std::string &testName) { + std::filesystem::remove_all("rocksdb_" + testName + "_db"); + std::filesystem::remove_all("rocksdb_" + testName + "_label_index"); + std::filesystem::remove_all("rocksdb_" + testName + "_label_property_index"); + std::filesystem::remove_all("rocksdb_" + testName + "_unique_constraints"); + std::filesystem::remove_all("rocksdb_" + testName + "_name_id_mapper"); + std::filesystem::remove_all("rocksdb_" + testName + "_id_name_mapper"); + std::filesystem::remove_all("rocksdb_" + testName + "_durability"); + std::filesystem::remove_all("rocksdb_" + testName + "_wal"); +} + +uint64_t GetRealNumberOfEntriesInRocksDB(rocksdb::TransactionDB *disk_storage) { + uint64_t num_keys = 0; + disk_storage->GetAggregatedIntProperty("rocksdb.estimate-num-keys", &num_keys); + return num_keys; +} + +} // namespace disk_test_utils diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index f2ce07479..ad01586dd 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -16,6 +16,7 @@ #include "communication/bolt/v1/value.hpp" #include "communication/result_stream_faker.hpp" #include "csv/parsing.hpp" +#include "disk_test_utils.hpp" #include "glue/communication.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -27,6 +28,7 @@ #include "query/stream.hpp" #include "query/typed_value.hpp" #include "query_common.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" #include "storage/v2/property_value.hpp" #include "utils/logging.hpp" @@ -46,11 +48,26 @@ auto ToEdgeList(const memgraph::communication::bolt::Value &v) { // TODO: This is not a unit test, but tests/integration dir is chaotic at the // moment. After tests refactoring is done, move/rename this. +template <typename StorageType> class InterpreterTest : public ::testing::Test { public: - memgraph::storage::Storage db_; - std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter"}; - memgraph::query::InterpreterContext interpreter_context{&db_, {}, data_directory}; + const std::string testSuite = "interpreter"; + const std::string testSuiteCsv = "interpreter_csv"; + + InterpreterTest() + : data_directory(std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter"), + interpreter_context(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), + {.execution_timeout_sec = 600}, data_directory) {} + + std::filesystem::path data_directory; + memgraph::query::InterpreterContext interpreter_context; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + disk_test_utils::RemoveRocksDbDirs(testSuiteCsv); + } + } InterpreterFaker default_interpreter{&interpreter_context}; @@ -67,23 +84,26 @@ class InterpreterTest : public ::testing::Test { } }; -TEST_F(InterpreterTest, MultiplePulls) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(InterpreterTest, StorageTypes); + +TYPED_TEST(InterpreterTest, MultiplePulls) { { - auto [stream, qid] = Prepare("UNWIND [1,2,3,4,5] as n RETURN n"); + auto [stream, qid] = this->Prepare("UNWIND [1,2,3,4,5] as n RETURN n"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "n"); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 1); - Pull(&stream, 2); + this->Pull(&stream, 2); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults().size(), 3U); ASSERT_EQ(stream.GetResults()[1][0].ValueInt(), 2); ASSERT_EQ(stream.GetResults()[2][0].ValueInt(), 3); - Pull(&stream); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults().size(), 5U); @@ -94,9 +114,9 @@ TEST_F(InterpreterTest, MultiplePulls) { // Run query with different ast twice to see if query executes correctly when // ast is read from cache. -TEST_F(InterpreterTest, AstCache) { +TYPED_TEST(InterpreterTest, AstCache) { { - auto stream = Interpret("RETURN 2 + 3"); + auto stream = this->Interpret("RETURN 2 + 3"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "2 + 3"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -105,42 +125,42 @@ TEST_F(InterpreterTest, AstCache) { } { // Cached ast, different literals. - auto stream = Interpret("RETURN 5 + 4"); + auto stream = this->Interpret("RETURN 5 + 4"); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 9); } { // Different ast (because of different types). - auto stream = Interpret("RETURN 5.5 + 4"); + auto stream = this->Interpret("RETURN 5.5 + 4"); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 9.5); } { // Cached ast, same literals. - auto stream = Interpret("RETURN 2 + 3"); + auto stream = this->Interpret("RETURN 2 + 3"); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 5); } { // Cached ast, different literals. - auto stream = Interpret("RETURN 10.5 + 1"); + auto stream = this->Interpret("RETURN 10.5 + 1"); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); } { // Cached ast, same literals, different whitespaces. - auto stream = Interpret("RETURN 10.5 + 1"); + auto stream = this->Interpret("RETURN 10.5 + 1"); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); } { // Cached ast, same literals, different named header. - auto stream = Interpret("RETURN 10.5+1"); + auto stream = this->Interpret("RETURN 10.5+1"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "10.5+1"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -150,10 +170,10 @@ TEST_F(InterpreterTest, AstCache) { } // Run query with same ast multiple times with different parameters. -TEST_F(InterpreterTest, Parameters) { +TYPED_TEST(InterpreterTest, Parameters) { { - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue(10)}, - {"a b", memgraph::storage::PropertyValue(15)}}); + auto stream = this->Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue(10)}, + {"a b", memgraph::storage::PropertyValue(15)}}); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -162,9 +182,9 @@ TEST_F(InterpreterTest, Parameters) { } { // Not needed parameter. - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue(10)}, - {"a b", memgraph::storage::PropertyValue(15)}, - {"c", memgraph::storage::PropertyValue(10)}}); + auto stream = this->Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue(10)}, + {"a b", memgraph::storage::PropertyValue(15)}, + {"c", memgraph::storage::PropertyValue(10)}}); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -173,18 +193,18 @@ TEST_F(InterpreterTest, Parameters) { } { // Cached ast, different parameters. - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue("da")}, - {"a b", memgraph::storage::PropertyValue("ne")}}); + auto stream = this->Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue("da")}, + {"a b", memgraph::storage::PropertyValue("ne")}}); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "dane"); } { // Non-primitive literal. - auto stream = - Interpret("RETURN $2", {{"2", memgraph::storage::PropertyValue(std::vector<memgraph::storage::PropertyValue>{ - memgraph::storage::PropertyValue(5), memgraph::storage::PropertyValue(2), - memgraph::storage::PropertyValue(3)})}}); + auto stream = this->Interpret("RETURN $2", + {{"2", memgraph::storage::PropertyValue(std::vector<memgraph::storage::PropertyValue>{ + memgraph::storage::PropertyValue(5), memgraph::storage::PropertyValue(2), + memgraph::storage::PropertyValue(3)})}}); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 1U); auto result = memgraph::query::test_common::ToIntList(memgraph::glue::ToTypedValue(stream.GetResults()[0][0])); @@ -192,21 +212,22 @@ TEST_F(InterpreterTest, Parameters) { } { // Cached ast, unprovided parameter. - ASSERT_THROW(Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue("da")}, - {"ab", memgraph::storage::PropertyValue("ne")}}), + ASSERT_THROW(this->Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::PropertyValue("da")}, + {"ab", memgraph::storage::PropertyValue("ne")}}), memgraph::query::UnprovidedParameterError); } } // Run CREATE/MATCH/MERGE queries with property map -TEST_F(InterpreterTest, ParametersAsPropertyMap) { +TYPED_TEST(InterpreterTest, ParametersAsPropertyMap) { { std::map<std::string, memgraph::storage::PropertyValue> property_map{}; property_map["name"] = memgraph::storage::PropertyValue("name1"); property_map["age"] = memgraph::storage::PropertyValue(25); - auto stream = Interpret("CREATE (n $prop) RETURN n", { - {"prop", memgraph::storage::PropertyValue(property_map)}, - }); + auto stream = + this->Interpret("CREATE (n $prop) RETURN n", { + {"prop", memgraph::storage::PropertyValue(property_map)}, + }); ASSERT_EQ(stream.GetHeader().size(), 1U); ASSERT_EQ(stream.GetHeader()[0], "n"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -219,11 +240,11 @@ TEST_F(InterpreterTest, ParametersAsPropertyMap) { std::map<std::string, memgraph::storage::PropertyValue> property_map{}; property_map["name"] = memgraph::storage::PropertyValue("name1"); property_map["age"] = memgraph::storage::PropertyValue(25); - Interpret("CREATE (:Person)"); - auto stream = Interpret("MATCH (m: Person) CREATE (n $prop) RETURN n", - { - {"prop", memgraph::storage::PropertyValue(property_map)}, - }); + this->Interpret("CREATE (:Person)"); + auto stream = this->Interpret("MATCH (m: Person) CREATE (n $prop) RETURN n", + { + {"prop", memgraph::storage::PropertyValue(property_map)}, + }); ASSERT_EQ(stream.GetHeader().size(), 1U); ASSERT_EQ(stream.GetHeader()[0], "n"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -236,10 +257,10 @@ TEST_F(InterpreterTest, ParametersAsPropertyMap) { std::map<std::string, memgraph::storage::PropertyValue> property_map{}; property_map["name"] = memgraph::storage::PropertyValue("name1"); property_map["weight"] = memgraph::storage::PropertyValue(121); - auto stream = - Interpret("CREATE ()-[r:TO $prop]->() RETURN r", { - {"prop", memgraph::storage::PropertyValue(property_map)}, - }); + auto stream = this->Interpret("CREATE ()-[r:TO $prop]->() RETURN r", + { + {"prop", memgraph::storage::PropertyValue(property_map)}, + }); ASSERT_EQ(stream.GetHeader().size(), 1U); ASSERT_EQ(stream.GetHeader()[0], "r"); ASSERT_EQ(stream.GetResults().size(), 1U); @@ -252,42 +273,52 @@ TEST_F(InterpreterTest, ParametersAsPropertyMap) { std::map<std::string, memgraph::storage::PropertyValue> property_map{}; property_map["name"] = memgraph::storage::PropertyValue("name1"); property_map["age"] = memgraph::storage::PropertyValue(15); - ASSERT_THROW(Interpret("MATCH (n $prop) RETURN n", - { - {"prop", memgraph::storage::PropertyValue(property_map)}, - }), + ASSERT_THROW(this->Interpret("MATCH (n $prop) RETURN n", + { + {"prop", memgraph::storage::PropertyValue(property_map)}, + }), memgraph::query::SemanticException); } { std::map<std::string, memgraph::storage::PropertyValue> property_map{}; property_map["name"] = memgraph::storage::PropertyValue("name1"); property_map["age"] = memgraph::storage::PropertyValue(15); - ASSERT_THROW(Interpret("MERGE (n $prop) RETURN n", - { - {"prop", memgraph::storage::PropertyValue(property_map)}, - }), + ASSERT_THROW(this->Interpret("MERGE (n $prop) RETURN n", + { + {"prop", memgraph::storage::PropertyValue(property_map)}, + }), memgraph::query::SemanticException); } } // Test bfs end to end. -TEST_F(InterpreterTest, Bfs) { +TYPED_TEST(InterpreterTest, Bfs) { srand(0); - const auto kNumLevels = 10; - const auto kNumNodesPerLevel = 100; - const auto kNumEdgesPerNode = 100; - const auto kNumUnreachableNodes = 1000; - const auto kNumUnreachableEdges = 100000; + auto kNumLevels = 10; + auto kNumNodesPerLevel = 100; + auto kNumEdgesPerNode = 100; + auto kNumUnreachableNodes = 1000; + auto kNumUnreachableEdges = 100000; + auto kResCoeff = 5; const auto kReachable = "reachable"; const auto kId = "id"; + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + kNumLevels = 5; + kNumNodesPerLevel = 20; + kNumEdgesPerNode = 20; + kNumUnreachableNodes = 200; + kNumUnreachableEdges = 20000; + kResCoeff = 4; + } + std::vector<std::vector<memgraph::query::VertexAccessor>> levels(kNumLevels); int id = 0; // Set up. { - auto storage_dba = db_.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->interpreter_context.db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto add_node = [&](int level, bool reachable) { auto node = dba.InsertVertex(); MG_ASSERT(node.SetProperty(dba.NameToProperty(kId), memgraph::storage::PropertyValue(id++)).HasValue()); @@ -341,7 +372,7 @@ TEST_F(InterpreterTest, Bfs) { ASSERT_FALSE(dba.Commit().HasError()); } - auto stream = Interpret( + auto stream = this->Interpret( "MATCH (n {id: 0})-[r *bfs..5 (e, n | n.reachable and " "e.reachable)]->(m) RETURN n, r, m"); @@ -349,9 +380,8 @@ TEST_F(InterpreterTest, Bfs) { EXPECT_EQ(stream.GetHeader()[0], "n"); EXPECT_EQ(stream.GetHeader()[1], "r"); EXPECT_EQ(stream.GetHeader()[2], "m"); - ASSERT_EQ(stream.GetResults().size(), 5 * kNumNodesPerLevel); + ASSERT_EQ(stream.GetResults().size(), kResCoeff * kNumNodesPerLevel); - auto dba = db_.Access(); int expected_level = 1; int remaining_nodes_in_level = kNumNodesPerLevel; std::unordered_set<int64_t> matched_ids; @@ -386,24 +416,23 @@ TEST_F(InterpreterTest, Bfs) { } // Test shortest path end to end. -TEST_F(InterpreterTest, ShortestPath) { +TYPED_TEST(InterpreterTest, ShortestPath) { const auto test_shortest_path = [this](const bool use_duration) { const auto get_weight = [use_duration](const auto value) { return fmt::format(fmt::runtime(use_duration ? "DURATION('PT{}S')" : "{}"), value); }; - Interpret( + this->Interpret( fmt::format("CREATE (n:A {{x: 1}}), (m:B {{x: 2}}), (l:C {{x: 1}}), (n)-[:r1 {{w: {} " "}}]->(m)-[:r2 {{w: {}}}]->(l), (n)-[:r3 {{w: {}}}]->(l)", get_weight(1), get_weight(2), get_weight(4))); - auto stream = Interpret("MATCH (n)-[e *wshortest 5 (e, n | e.w) ]->(m) return e"); + auto stream = this->Interpret("MATCH (n)-[e *wshortest 5 (e, n | e.w) ]->(m) return e"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "e"); ASSERT_EQ(stream.GetResults().size(), 3U); - auto dba = db_.Access(); std::vector<std::vector<std::string>> expected_results{{"r1"}, {"r2"}, {"r1", "r2"}}; for (const auto &result : stream.GetResults()) { @@ -427,7 +456,7 @@ TEST_F(InterpreterTest, ShortestPath) { EXPECT_TRUE(any_match); } - Interpret("MATCH (n) DETACH DELETE n"); + this->Interpret("MATCH (n) DETACH DELETE n"); }; static constexpr bool kUseNumeric{false}; @@ -442,22 +471,21 @@ TEST_F(InterpreterTest, ShortestPath) { } } -TEST_F(InterpreterTest, AllShortestById) { - auto stream_init = Interpret( +TYPED_TEST(InterpreterTest, AllShortestById) { + auto stream_init = this->Interpret( "CREATE (n:A {x: 1}), (m:B {x: 2}), (l:C {x: 3}), (k:D {x: 4}), (n)-[:r1 {w: 1 " "}]->(m)-[:r2 {w: 2}]->(l), (n)-[:r3 {w: 4}]->(l), (k)-[:r4 {w: 3}]->(l) return id(n), id(l)"); auto id_n = stream_init.GetResults().front()[0].ValueInt(); auto id_l = stream_init.GetResults().front()[1].ValueInt(); - auto stream = Interpret( + auto stream = this->Interpret( fmt::format("MATCH (n)-[e *allshortest 5 (e, n | e.w) ]->(l) WHERE id(n)={} AND id(l)={} return e", id_n, id_l)); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "e"); ASSERT_EQ(stream.GetResults().size(), 1U); - auto dba = db_.Access(); std::vector<std::string> expected_result = {"r1", "r2"}; const auto &result = stream.GetResults()[0]; @@ -472,70 +500,71 @@ TEST_F(InterpreterTest, AllShortestById) { EXPECT_TRUE(expected_result == datum); - Interpret("MATCH (n) DETACH DELETE n"); + this->Interpret("MATCH (n) DETACH DELETE n"); } -TEST_F(InterpreterTest, CreateLabelIndexInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE INDEX ON :X"), memgraph::query::IndexInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, CreateLabelIndexInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("CREATE INDEX ON :X"), memgraph::query::IndexInMulticommandTxException); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, CreateLabelPropertyIndexInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE INDEX ON :X(y)"), memgraph::query::IndexInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, CreateLabelPropertyIndexInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("CREATE INDEX ON :X(y)"), memgraph::query::IndexInMulticommandTxException); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, CreateExistenceConstraintInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a)"), +TYPED_TEST(InterpreterTest, CreateExistenceConstraintInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a)"), memgraph::query::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, CreateUniqueConstraintInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE"), +TYPED_TEST(InterpreterTest, CreateUniqueConstraintInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE"), memgraph::query::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, ShowIndexInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW INDEX INFO"), memgraph::query::InfoInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, ShowIndexInfoInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("SHOW INDEX INFO"), memgraph::query::InfoInMulticommandTxException); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, ShowConstraintInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW CONSTRAINT INFO"), memgraph::query::InfoInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, ShowConstraintInfoInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("SHOW CONSTRAINT INFO"), memgraph::query::InfoInMulticommandTxException); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, ShowStorageInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW STORAGE INFO"), memgraph::query::InfoInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, ShowStorageInfoInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("SHOW STORAGE INFO"), memgraph::query::InfoInMulticommandTxException); + this->Interpret("ROLLBACK"); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(InterpreterTest, ExistenceConstraintTest) { - Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a);"); - Interpret("CREATE (:A{a:1})"); - Interpret("CREATE (:A{a:2})"); - ASSERT_THROW(Interpret("CREATE (:A)"), memgraph::query::QueryException); - Interpret("MATCH (n:A{a:2}) SET n.a=3"); - Interpret("CREATE (:A{a:2})"); - Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); - Interpret("CREATE (n:A{a:2})"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.b);"), memgraph::query::QueryRuntimeException); +TYPED_TEST(InterpreterTest, ExistenceConstraintTest) { + this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a);"); + this->Interpret("CREATE (:A{a:1})"); + this->Interpret("CREATE (:A{a:2})"); + ASSERT_THROW(this->Interpret("CREATE (:A)"), memgraph::query::QueryException); + this->Interpret("MATCH (n:A{a:2}) SET n.a=3"); + this->Interpret("CREATE (:A{a:2})"); + this->Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); + this->Interpret("CREATE (n:A{a:2})"); + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.b);"), + memgraph::query::QueryRuntimeException); } -TEST_F(InterpreterTest, UniqueConstraintTest) { +TYPED_TEST(InterpreterTest, UniqueConstraintTest) { // Empty property list should result with syntax exception. - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::SyntaxException); - ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::SyntaxException); + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::SyntaxException); + ASSERT_THROW(this->Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::SyntaxException); // Too large list of properties should also result with syntax exception. { @@ -548,35 +577,36 @@ TEST_F(InterpreterTest, UniqueConstraintTest) { stream << " IS UNIQUE;"; std::string create_query = "CREATE CONSTRAINT" + stream.str(); std::string drop_query = "DROP CONSTRAINT" + stream.str(); - ASSERT_THROW(Interpret(create_query), memgraph::query::SyntaxException); - ASSERT_THROW(Interpret(drop_query), memgraph::query::SyntaxException); + ASSERT_THROW(this->Interpret(create_query), memgraph::query::SyntaxException); + ASSERT_THROW(this->Interpret(drop_query), memgraph::query::SyntaxException); } // Providing property list with duplicates results with syntax exception. - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), + memgraph::query::SyntaxException); + ASSERT_THROW(this->Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), memgraph::query::SyntaxException); - ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), memgraph::query::SyntaxException); // Commit of vertex should fail if a constraint is violated. - Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); - Interpret("CREATE (:A{a:1, b:2})"); - Interpret("CREATE (:A{a:1, b:3})"); - ASSERT_THROW(Interpret("CREATE (:A{a:1, b:2})"), memgraph::query::QueryException); + this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); + this->Interpret("CREATE (:A{a:1, b:2})"); + this->Interpret("CREATE (:A{a:1, b:3})"); + ASSERT_THROW(this->Interpret("CREATE (:A{a:1, b:2})"), memgraph::query::QueryException); // Attempt to create a constraint should fail if it's violated. - Interpret("CREATE (:A{a:1, c:2})"); - Interpret("CREATE (:A{a:1, c:2})"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.c IS UNIQUE;"), + this->Interpret("CREATE (:A{a:1, c:2})"); + this->Interpret("CREATE (:A{a:1, c:2})"); + ASSERT_THROW(this->Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.c IS UNIQUE;"), memgraph::query::QueryRuntimeException); - Interpret("MATCH (n:A{a:2, b:2}) SET n.a=1"); - Interpret("CREATE (:A{a:2})"); - Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); - Interpret("CREATE (n:A{a:2})"); + this->Interpret("MATCH (n:A{a:2, b:2}) SET n.a=1"); + this->Interpret("CREATE (:A{a:2})"); + this->Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); + this->Interpret("CREATE (n:A{a:2})"); // Show constraint info. { - auto stream = Interpret("SHOW CONSTRAINT INFO"); + auto stream = this->Interpret("SHOW CONSTRAINT INFO"); ASSERT_EQ(stream.GetHeader().size(), 3U); const auto &header = stream.GetHeader(); ASSERT_EQ(header[0], "constraint type"); @@ -594,15 +624,15 @@ TEST_F(InterpreterTest, UniqueConstraintTest) { } // Drop constraint. - Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); + this->Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); // Removing the same constraint twice should not throw any exception. - Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); + this->Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); } -TEST_F(InterpreterTest, ExplainQuery) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); +TYPED_TEST(InterpreterTest, ExplainQuery) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + auto stream = this->Interpret("EXPLAIN MATCH (n) RETURN *;"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); std::vector<std::string> expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; @@ -614,53 +644,53 @@ TEST_F(InterpreterTest, ExplainQuery) { ++expected_it; } // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) RETURN *;"); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ExplainQueryMultiplePulls) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto [stream, qid] = Prepare("EXPLAIN MATCH (n) RETURN *;"); +TYPED_TEST(InterpreterTest, ExplainQueryMultiplePulls) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + auto [stream, qid] = this->Prepare("EXPLAIN MATCH (n) RETURN *;"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); std::vector<std::string> expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetResults().size(), 1); auto expected_it = expected_rows.begin(); ASSERT_EQ(stream.GetResults()[0].size(), 1U); EXPECT_EQ(stream.GetResults()[0].front().ValueString(), *expected_it); ++expected_it; - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetResults().size(), 2); ASSERT_EQ(stream.GetResults()[1].size(), 1U); EXPECT_EQ(stream.GetResults()[1].front().ValueString(), *expected_it); ++expected_it; - Pull(&stream); + this->Pull(&stream); ASSERT_EQ(stream.GetResults().size(), 3); ASSERT_EQ(stream.GetResults()[2].size(), 1U); EXPECT_EQ(stream.GetResults()[2].front().ValueString(), *expected_it); // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) RETURN *;"); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ExplainQueryInMulticommandTransaction) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - Interpret("BEGIN"); - auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); - Interpret("COMMIT"); +TYPED_TEST(InterpreterTest, ExplainQueryInMulticommandTransaction) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + this->Interpret("BEGIN"); + auto stream = this->Interpret("EXPLAIN MATCH (n) RETURN *;"); + this->Interpret("COMMIT"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); std::vector<std::string> expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; @@ -672,19 +702,19 @@ TEST_F(InterpreterTest, ExplainQueryInMulticommandTransaction) { ++expected_it; } // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) RETURN *;"); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ExplainQueryWithParams) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +TYPED_TEST(InterpreterTest, ExplainQueryWithParams) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); auto stream = - Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}}); + this->Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}}); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); std::vector<std::string> expected_rows{" * Produce {n}", " * Filter", " * ScanAll (n)", " * Once"}; @@ -696,18 +726,18 @@ TEST_F(InterpreterTest, ExplainQueryWithParams) { ++expected_it; } // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue("something else")}}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue("something else")}}); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ProfileQuery) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = Interpret("PROFILE MATCH (n) RETURN *;"); +TYPED_TEST(InterpreterTest, ProfileQuery) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + auto stream = this->Interpret("PROFILE MATCH (n) RETURN *;"); std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"}; @@ -719,61 +749,61 @@ TEST_F(InterpreterTest, ProfileQuery) { ++expected_it; } // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) RETURN *;"); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ProfileQueryMultiplePulls) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto [stream, qid] = Prepare("PROFILE MATCH (n) RETURN *;"); +TYPED_TEST(InterpreterTest, ProfileQueryMultiplePulls) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + auto [stream, qid] = this->Prepare("PROFILE MATCH (n) RETURN *;"); std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"}; auto expected_it = expected_rows.begin(); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0].size(), 4U); ASSERT_EQ(stream.GetResults()[0][0].ValueString(), *expected_it); ++expected_it; - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetResults().size(), 2U); ASSERT_EQ(stream.GetResults()[1].size(), 4U); ASSERT_EQ(stream.GetResults()[1][0].ValueString(), *expected_it); ++expected_it; - Pull(&stream); + this->Pull(&stream); ASSERT_EQ(stream.GetResults().size(), 3U); ASSERT_EQ(stream.GetResults()[2].size(), 4U); ASSERT_EQ(stream.GetResults()[2][0].ValueString(), *expected_it); // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) RETURN *;"); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ProfileQueryInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("PROFILE MATCH (n) RETURN *;"), memgraph::query::ProfileInMulticommandTxException); - Interpret("ROLLBACK"); +TYPED_TEST(InterpreterTest, ProfileQueryInMulticommandTransaction) { + this->Interpret("BEGIN"); + ASSERT_THROW(this->Interpret("PROFILE MATCH (n) RETURN *;"), memgraph::query::ProfileInMulticommandTxException); + this->Interpret("ROLLBACK"); } -TEST_F(InterpreterTest, ProfileQueryWithParams) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +TYPED_TEST(InterpreterTest, ProfileQueryWithParams) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); auto stream = - Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}}); + this->Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}}); std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"}; @@ -785,18 +815,18 @@ TEST_F(InterpreterTest, ProfileQueryWithParams) { ++expected_it; } // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue("something else")}}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue("something else")}}); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, ProfileQueryWithLiterals) { - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {}); +TYPED_TEST(InterpreterTest, ProfileQueryWithLiterals) { + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U); + auto stream = this->Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {}); std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; EXPECT_EQ(stream.GetHeader(), expected_header); std::vector<std::string> expected_rows{"* EmptyResult", "* CreateNode", "* Unwind", "* Once"}; @@ -808,25 +838,25 @@ TEST_F(InterpreterTest, ProfileQueryWithLiterals) { ++expected_it; } // We should have a plan cache for UNWIND ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); // We should have AST cache for PROFILE ... and for inner UNWIND ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("UNWIND range(42, 4242) AS x CREATE (:Node {id: x});", {}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); + this->Interpret("UNWIND range(42, 4242) AS x CREATE (:Node {id: x});", {}); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U); } -TEST_F(InterpreterTest, Transactions) { - auto &interpreter = default_interpreter.interpreter; +TYPED_TEST(InterpreterTest, Transactions) { + auto &interpreter = this->default_interpreter.interpreter; { ASSERT_THROW(interpreter.CommitTransaction(), memgraph::query::ExplicitTransactionUsageException); ASSERT_THROW(interpreter.RollbackTransaction(), memgraph::query::ExplicitTransactionUsageException); interpreter.BeginTransaction(); ASSERT_THROW(interpreter.BeginTransaction(), memgraph::query::ExplicitTransactionUsageException); - auto [stream, qid] = Prepare("RETURN 2"); + auto [stream, qid] = this->Prepare("RETURN 2"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "2"); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults()[0].size(), 1U); @@ -835,10 +865,10 @@ TEST_F(InterpreterTest, Transactions) { } { interpreter.BeginTransaction(); - auto [stream, qid] = Prepare("RETURN 2"); + auto [stream, qid] = this->Prepare("RETURN 2"); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "2"); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults()[0].size(), 1U); @@ -847,40 +877,40 @@ TEST_F(InterpreterTest, Transactions) { } } -TEST_F(InterpreterTest, Qid) { - auto &interpreter = default_interpreter.interpreter; +TYPED_TEST(InterpreterTest, Qid) { + auto &interpreter = this->default_interpreter.interpreter; { interpreter.BeginTransaction(); - auto [stream, qid] = Prepare("RETURN 2"); + auto [stream, qid] = this->Prepare("RETURN 2"); ASSERT_TRUE(qid); - ASSERT_THROW(Pull(&stream, {}, *qid + 1), memgraph::query::InvalidArgumentsException); + ASSERT_THROW(this->Pull(&stream, {}, *qid + 1), memgraph::query::InvalidArgumentsException); interpreter.RollbackTransaction(); } { interpreter.BeginTransaction(); - auto [stream1, qid1] = Prepare("UNWIND(range(1,3)) as n RETURN n"); + auto [stream1, qid1] = this->Prepare("UNWIND(range(1,3)) as n RETURN n"); ASSERT_TRUE(qid1); ASSERT_EQ(stream1.GetHeader().size(), 1U); EXPECT_EQ(stream1.GetHeader()[0], "n"); - auto [stream2, qid2] = Prepare("UNWIND(range(4,6)) as n RETURN n"); + auto [stream2, qid2] = this->Prepare("UNWIND(range(4,6)) as n RETURN n"); ASSERT_TRUE(qid2); ASSERT_EQ(stream2.GetHeader().size(), 1U); EXPECT_EQ(stream2.GetHeader()[0], "n"); - Pull(&stream1, 1, qid1); + this->Pull(&stream1, 1, qid1); ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); ASSERT_TRUE(stream1.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream1.GetResults().size(), 1U); ASSERT_EQ(stream1.GetResults()[0].size(), 1U); ASSERT_EQ(stream1.GetResults()[0][0].ValueInt(), 1); - auto [stream3, qid3] = Prepare("UNWIND(range(7,9)) as n RETURN n"); + auto [stream3, qid3] = this->Prepare("UNWIND(range(7,9)) as n RETURN n"); ASSERT_TRUE(qid3); ASSERT_EQ(stream3.GetHeader().size(), 1U); EXPECT_EQ(stream3.GetHeader()[0], "n"); - Pull(&stream2, {}, qid2); + this->Pull(&stream2, {}, qid2); ASSERT_EQ(stream2.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream2.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream2.GetResults().size(), 3U); @@ -889,14 +919,14 @@ TEST_F(InterpreterTest, Qid) { ASSERT_EQ(stream2.GetResults()[1][0].ValueInt(), 5); ASSERT_EQ(stream2.GetResults()[2][0].ValueInt(), 6); - Pull(&stream3, 1, qid3); + this->Pull(&stream3, 1, qid3); ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); ASSERT_TRUE(stream3.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream3.GetResults().size(), 1U); ASSERT_EQ(stream3.GetResults()[0].size(), 1U); ASSERT_EQ(stream3.GetResults()[0][0].ValueInt(), 7); - Pull(&stream1, {}, qid1); + this->Pull(&stream1, {}, qid1); ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream1.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream1.GetResults().size(), 3U); @@ -904,7 +934,7 @@ TEST_F(InterpreterTest, Qid) { ASSERT_EQ(stream1.GetResults()[1][0].ValueInt(), 2); ASSERT_EQ(stream1.GetResults()[2][0].ValueInt(), 3); - Pull(&stream3); + this->Pull(&stream3); ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream3.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream3.GetResults().size(), 3U); @@ -975,7 +1005,7 @@ std::string CreateRow(const std::vector<std::string> &columns, const std::string } } // namespace -TEST_F(InterpreterTest, LoadCsvClause) { +TYPED_TEST(InterpreterTest, LoadCsvClause) { auto dir_manager = TmpDirManager("csv_directory"); const auto csv_path = dir_manager.Path() / "file.csv"; auto writer = FileWriter(csv_path); @@ -999,17 +1029,17 @@ TEST_F(InterpreterTest, LoadCsvClause) { { const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x.A)", csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); + auto [stream, qid] = this->Prepare(query); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "x.A"); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults().size(), 1U); ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "a"); - Pull(&stream, 1); + this->Pull(&stream, 1); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults().size(), 2U); @@ -1019,11 +1049,11 @@ TEST_F(InterpreterTest, LoadCsvClause) { { const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x.C)", csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); + auto [stream, qid] = this->Prepare(query); ASSERT_EQ(stream.GetHeader().size(), 1U); EXPECT_EQ(stream.GetHeader()[0], "x.C"); - Pull(&stream); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("has_more"), 1); ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); ASSERT_EQ(stream.GetResults().size(), 2U); @@ -1032,26 +1062,26 @@ TEST_F(InterpreterTest, LoadCsvClause) { } } -TEST_F(InterpreterTest, CacheableQueries) { +TYPED_TEST(InterpreterTest, CacheableQueries) { // This should be cached { SCOPED_TRACE("Cacheable query"); - Interpret("RETURN 1"); - EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + this->Interpret("RETURN 1"); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); } { SCOPED_TRACE("Uncacheable query"); // Queries which are calling procedure should not be cached because the // result signature could be changed - Interpret("CALL mg.load_all()"); - EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); + this->Interpret("CALL mg.load_all()"); + EXPECT_EQ(this->interpreter_context.ast_cache.size(), 1U); + EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U); } } -TEST_F(InterpreterTest, AllowLoadCsvConfig) { +TYPED_TEST(InterpreterTest, AllowLoadCsvConfig) { const auto check_load_csv_queries = [&](const bool allow_load_csv) { TmpDirManager directory_manager{"allow_load_csv"}; const auto csv_path = directory_manager.Path() / "file.csv"; @@ -1066,7 +1096,9 @@ TEST_F(InterpreterTest, AllowLoadCsvConfig) { "row"}; memgraph::query::InterpreterContext csv_interpreter_context{ - &db_, {.query = {.allow_load_csv = allow_load_csv}}, directory_manager.Path()}; + std::make_unique<TypeParam>(disk_test_utils::GenerateOnDiskConfig(this->testSuiteCsv)), + {.query = {.allow_load_csv = allow_load_csv}}, + directory_manager.Path()}; InterpreterFaker interpreter_faker{&csv_interpreter_context}; for (const auto &query : queries) { if (allow_load_csv) { @@ -1093,18 +1125,18 @@ void AssertAllValuesAreZero(const std::map<std::string, memgraph::communication: } } -TEST_F(InterpreterTest, ExecutionStatsIsValid) { +TYPED_TEST(InterpreterTest, ExecutionStatsIsValid) { { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("MATCH (n) DELETE n;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("stats"), 0); } { std::array stats_keys{"nodes-created", "nodes-deleted", "relationships-created", "relationships-deleted", "properties-set", "labels-added", "labels-removed"}; - auto [stream, qid] = Prepare("CREATE ();"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE ();"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("stats"), 1); ASSERT_TRUE(stream.GetSummary().at("stats").IsMap()); @@ -1115,26 +1147,26 @@ TEST_F(InterpreterTest, ExecutionStatsIsValid) { } } -TEST_F(InterpreterTest, ExecutionStatsValues) { +TYPED_TEST(InterpreterTest, ExecutionStatsValues) { { - auto [stream, qid] = Prepare("CREATE (),(),(),();"); + auto [stream, qid] = this->Prepare("CREATE (),(),(),();"); - Pull(&stream); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["nodes-created"].ValueInt(), 4); AssertAllValuesAreZero(stats, {"nodes-created"}); } { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("MATCH (n) DELETE n;"); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 4); AssertAllValuesAreZero(stats, {"nodes-deleted"}); } { - auto [stream, qid] = Prepare("CREATE (n)-[:TO]->(m), (n)-[:TO]->(m), (n)-[:TO]->(m);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE (n)-[:TO]->(m), (n)-[:TO]->(m), (n)-[:TO]->(m);"); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["nodes-created"].ValueInt(), 2); @@ -1142,8 +1174,8 @@ TEST_F(InterpreterTest, ExecutionStatsValues) { AssertAllValuesAreZero(stats, {"nodes-created", "relationships-created"}); } { - auto [stream, qid] = Prepare("MATCH (n) DETACH DELETE n;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("MATCH (n) DETACH DELETE n;"); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 2); @@ -1151,8 +1183,8 @@ TEST_F(InterpreterTest, ExecutionStatsValues) { AssertAllValuesAreZero(stats, {"nodes-deleted", "relationships-deleted"}); } { - auto [stream, qid] = Prepare("CREATE (:L1:L2:L3), (:L1), (:L1), (:L2);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE (:L1:L2:L3), (:L1), (:L1), (:L2);"); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["nodes-created"].ValueInt(), 4); @@ -1160,8 +1192,8 @@ TEST_F(InterpreterTest, ExecutionStatsValues) { AssertAllValuesAreZero(stats, {"nodes-created", "labels-added"}); } { - auto [stream, qid] = Prepare("MATCH (n:L1) SET n.name='test';"); - Pull(&stream); + auto [stream, qid] = this->Prepare("MATCH (n:L1) SET n.name='test';"); + this->Pull(&stream); auto stats = stream.GetSummary().at("stats").ValueMap(); ASSERT_EQ(stats["properties-set"].ValueInt(), 3); @@ -1169,16 +1201,16 @@ TEST_F(InterpreterTest, ExecutionStatsValues) { } } -TEST_F(InterpreterTest, NotificationsValidStructure) { +TYPED_TEST(InterpreterTest, NotificationsValidStructure) { { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("MATCH (n) DELETE n;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 0); } { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE INDEX ON :Person(id);"); + this->Pull(&stream); // Assert notifications list ASSERT_EQ(stream.GetSummary().count("notifications"), 1); @@ -1200,10 +1232,10 @@ TEST_F(InterpreterTest, NotificationsValidStructure) { } } -TEST_F(InterpreterTest, IndexInfoNotifications) { +TYPED_TEST(InterpreterTest, IndexInfoNotifications) { { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE INDEX ON :Person;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1215,8 +1247,8 @@ TEST_F(InterpreterTest, IndexInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE INDEX ON :Person(id);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1228,8 +1260,8 @@ TEST_F(InterpreterTest, IndexInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE INDEX ON :Person(id);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1241,8 +1273,8 @@ TEST_F(InterpreterTest, IndexInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP INDEX ON :Person(id);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1254,8 +1286,8 @@ TEST_F(InterpreterTest, IndexInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP INDEX ON :Person(id);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1268,10 +1300,10 @@ TEST_F(InterpreterTest, IndexInfoNotifications) { } } -TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { +TYPED_TEST(InterpreterTest, ConstraintUniqueInfoNotifications) { { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1284,8 +1316,8 @@ TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1298,8 +1330,8 @@ TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1312,8 +1344,8 @@ TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1327,10 +1359,10 @@ TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { } } -TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { +TYPED_TEST(InterpreterTest, ConstraintExistsInfoNotifications) { { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1342,8 +1374,8 @@ TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1355,8 +1387,8 @@ TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1368,8 +1400,8 @@ TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1382,12 +1414,12 @@ TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { } } -TEST_F(InterpreterTest, TriggerInfoNotifications) { +TYPED_TEST(InterpreterTest, TriggerInfoNotifications) { { - auto [stream, qid] = Prepare( + auto [stream, qid] = this->Prepare( "CREATE TRIGGER bestTriggerEver ON CREATE AFTER COMMIT EXECUTE " "CREATE ();"); - Pull(&stream); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1399,8 +1431,8 @@ TEST_F(InterpreterTest, TriggerInfoNotifications) { ASSERT_EQ(notification["description"].ValueString(), ""); } { - auto [stream, qid] = Prepare("DROP TRIGGER bestTriggerEver;"); - Pull(&stream); + auto [stream, qid] = this->Prepare("DROP TRIGGER bestTriggerEver;"); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); @@ -1413,7 +1445,7 @@ TEST_F(InterpreterTest, TriggerInfoNotifications) { } } -TEST_F(InterpreterTest, LoadCsvClauseNotification) { +TYPED_TEST(InterpreterTest, LoadCsvClauseNotification) { auto dir_manager = TmpDirManager("csv_directory"); const auto csv_path = dir_manager.Path() / "file.csv"; auto writer = FileWriter(csv_path); @@ -1430,8 +1462,8 @@ TEST_F(InterpreterTest, LoadCsvClauseNotification) { const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x;)", csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); - Pull(&stream); + auto [stream, qid] = this->Prepare(query); + this->Pull(&stream); ASSERT_EQ(stream.GetSummary().count("notifications"), 1); auto notifications = stream.GetSummary().at("notifications").ValueList(); diff --git a/tests/unit/interpreter_faker.hpp b/tests/unit/interpreter_faker.hpp index 5b14a543e..9f6ee5c76 100644 --- a/tests/unit/interpreter_faker.hpp +++ b/tests/unit/interpreter_faker.hpp @@ -20,7 +20,7 @@ struct InterpreterFaker { } auto Prepare(const std::string &query, const std::map<std::string, memgraph::storage::PropertyValue> ¶ms = {}) { - ResultStreamFaker stream(interpreter_context->db); + ResultStreamFaker stream(interpreter_context->db.get()); const auto [header, _, qid] = interpreter.Prepare(query, params, nullptr); stream.Header(header); return std::make_pair(std::move(stream), qid); diff --git a/tests/unit/plan_pretty_print.cpp b/tests/unit/plan_pretty_print.cpp index da24db9a2..94b7342f9 100644 --- a/tests/unit/plan_pretty_print.cpp +++ b/tests/unit/plan_pretty_print.cpp @@ -11,11 +11,14 @@ #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/operator.hpp" #include "query/plan/pretty_print.hpp" #include "query_common.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; @@ -32,39 +35,54 @@ void PrintTo(const json &json, std::ostream *os) { *os << std::endl << json.dump using namespace nlohmann; +template <typename StorageType> class PrintToJsonTest : public ::testing::Test { protected: - PrintToJsonTest() : db(), dba(db.Access()) {} + const std::string testSuite = "plan_pretty_print"; + + PrintToJsonTest() + : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), + db(new StorageType(config)), + dba_storage(db->Access()), + dba(dba_storage.get()) {} + + ~PrintToJsonTest() { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } AstStorage storage; SymbolTable symbol_table; - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor dba; + memgraph::storage::Config config; + std::unique_ptr<memgraph::storage::Storage> db; + std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage; + memgraph::query::DbAccessor dba; Symbol GetSymbol(std::string name) { return symbol_table.CreateSymbol(name, true); } - void Check(LogicalOperator *root, std::string expected) { - memgraph::query::DbAccessor query_dba(&dba); - EXPECT_EQ(PlanToJson(query_dba, root), json::parse(expected)); - } + void Check(LogicalOperator *root, std::string expected) { EXPECT_EQ(PlanToJson(dba, root), json::parse(expected)); } }; -TEST_F(PrintToJsonTest, Once) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(PrintToJsonTest, StorageTypes); + +TYPED_TEST(PrintToJsonTest, Once) { std::shared_ptr<LogicalOperator> last_op; last_op = std::make_shared<Once>(); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "Once" })"); } -TEST_F(PrintToJsonTest, ScanAll) { +TYPED_TEST(PrintToJsonTest, ScanAll) { std::shared_ptr<LogicalOperator> last_op; - last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node")); + last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node")); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ScanAll", "output_symbol" : "node", @@ -72,11 +90,11 @@ TEST_F(PrintToJsonTest, ScanAll) { })"); } -TEST_F(PrintToJsonTest, ScanAllByLabel) { +TYPED_TEST(PrintToJsonTest, ScanAllByLabel) { std::shared_ptr<LogicalOperator> last_op; - last_op = std::make_shared<ScanAllByLabel>(nullptr, GetSymbol("node"), dba.NameToLabel("Label")); + last_op = std::make_shared<ScanAllByLabel>(nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label")); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ScanAllByLabel", "label" : "Label", @@ -85,15 +103,15 @@ TEST_F(PrintToJsonTest, ScanAllByLabel) { })"); } -TEST_F(PrintToJsonTest, ScanAllByLabelPropertyRange) { +TYPED_TEST(PrintToJsonTest, ScanAllByLabelPropertyRange) { { std::shared_ptr<LogicalOperator> last_op; last_op = std::make_shared<ScanAllByLabelPropertyRange>( - nullptr, GetSymbol("node"), dba.NameToLabel("Label"), dba.NameToProperty("prop"), "prop", + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", memgraph::utils::MakeBoundInclusive<Expression *>(LITERAL(1)), memgraph::utils::MakeBoundExclusive<Expression *>(LITERAL(20))); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ScanAllByLabelPropertyRange", "label" : "Label", @@ -113,10 +131,10 @@ TEST_F(PrintToJsonTest, ScanAllByLabelPropertyRange) { { std::shared_ptr<LogicalOperator> last_op; last_op = std::make_shared<ScanAllByLabelPropertyRange>( - nullptr, GetSymbol("node"), dba.NameToLabel("Label"), dba.NameToProperty("prop"), "prop", std::nullopt, - memgraph::utils::MakeBoundExclusive<Expression *>(LITERAL(20))); + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", + std::nullopt, memgraph::utils::MakeBoundExclusive<Expression *>(LITERAL(20))); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ScanAllByLabelPropertyRange", "label" : "Label", @@ -133,10 +151,10 @@ TEST_F(PrintToJsonTest, ScanAllByLabelPropertyRange) { { std::shared_ptr<LogicalOperator> last_op; last_op = std::make_shared<ScanAllByLabelPropertyRange>( - nullptr, GetSymbol("node"), dba.NameToLabel("Label"), dba.NameToProperty("prop"), "prop", + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", memgraph::utils::MakeBoundInclusive<Expression *>(LITERAL(1)), std::nullopt); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ScanAllByLabelPropertyRange", "label" : "Label", @@ -152,13 +170,13 @@ TEST_F(PrintToJsonTest, ScanAllByLabelPropertyRange) { } } -TEST_F(PrintToJsonTest, ScanAllByLabelPropertyValue) { +TYPED_TEST(PrintToJsonTest, ScanAllByLabelPropertyValue) { std::shared_ptr<LogicalOperator> last_op; - last_op = - std::make_shared<ScanAllByLabelPropertyValue>(nullptr, GetSymbol("node"), dba.NameToLabel("Label"), - dba.NameToProperty("prop"), "prop", ADD(LITERAL(21), LITERAL(21))); + last_op = std::make_shared<ScanAllByLabelPropertyValue>( + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", + ADD(LITERAL(21), LITERAL(21))); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "ScanAllByLabelPropertyValue", "label" : "Label", @@ -169,15 +187,15 @@ TEST_F(PrintToJsonTest, ScanAllByLabelPropertyValue) { })sep"); } -TEST_F(PrintToJsonTest, CreateNode) { +TYPED_TEST(PrintToJsonTest, CreateNode) { std::shared_ptr<LogicalOperator> last_op; - last_op = std::make_shared<CreateNode>(nullptr, - NodeCreationInfo{GetSymbol("node"), - {dba.NameToLabel("Label1"), dba.NameToLabel("Label2")}, - {{dba.NameToProperty("prop1"), LITERAL(5)}, - {dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}); + last_op = std::make_shared<CreateNode>( + nullptr, NodeCreationInfo{this->GetSymbol("node"), + {this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")}, + {{this->dba.NameToProperty("prop1"), LITERAL(5)}, + {this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "CreateNode", "node_info" : { @@ -192,21 +210,21 @@ TEST_F(PrintToJsonTest, CreateNode) { })"); } -TEST_F(PrintToJsonTest, CreateExpand) { - Symbol node1_sym = GetSymbol("node1"); - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node1")); +TYPED_TEST(PrintToJsonTest, CreateExpand) { + Symbol node1_sym = this->GetSymbol("node1"); + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node1")); last_op = std::make_shared<CreateExpand>( - NodeCreationInfo{ - GetSymbol("node2"), - {dba.NameToLabel("Label1"), dba.NameToLabel("Label2")}, - {{dba.NameToProperty("prop1"), LITERAL(5)}, {dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}, - EdgeCreationInfo{GetSymbol("edge"), - {{dba.NameToProperty("weight"), LITERAL(5.32)}}, - dba.NameToEdgeType("edge_type"), + NodeCreationInfo{this->GetSymbol("node2"), + {this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")}, + {{this->dba.NameToProperty("prop1"), LITERAL(5)}, + {this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}}, + EdgeCreationInfo{this->GetSymbol("edge"), + {{this->dba.NameToProperty("weight"), LITERAL(5.32)}}, + this->dba.NameToEdgeType("edge_type"), EdgeAtom::Direction::OUT}, last_op, node1_sym, false); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "CreateExpand", "node_info" : { @@ -235,15 +253,16 @@ TEST_F(PrintToJsonTest, CreateExpand) { })"); } -TEST_F(PrintToJsonTest, Expand) { - auto node1_sym = GetSymbol("node1"); +TYPED_TEST(PrintToJsonTest, Expand) { + auto node1_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); - last_op = std::make_shared<Expand>( - last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Direction::BOTH, - std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1"), dba.NameToEdgeType("EdgeType2")}, - false, memgraph::storage::View::OLD); + last_op = std::make_shared<Expand>(last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), + EdgeAtom::Direction::BOTH, + std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, + false, memgraph::storage::View::OLD); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "Expand", "input_symbol" : "node1", @@ -260,19 +279,20 @@ TEST_F(PrintToJsonTest, Expand) { })"); } -TEST_F(PrintToJsonTest, ExpandVariable) { - auto node1_sym = GetSymbol("node1"); +TYPED_TEST(PrintToJsonTest, ExpandVariable) { + auto node1_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<ExpandVariable>( - last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST, + last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT, - std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1"), dba.NameToEdgeType("EdgeType2")}, + std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, false, LITERAL(2), LITERAL(5), false, - ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"), - PROPERTY_LOOKUP("inner_node", dba.NameToProperty("unblocked"))}, + ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"), + PROPERTY_LOOKUP(this->dba, "inner_node", this->dba.NameToProperty("unblocked"))}, std::nullopt, std::nullopt); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "ExpandVariable", "input_symbol" : "node1", @@ -294,19 +314,21 @@ TEST_F(PrintToJsonTest, ExpandVariable) { })sep"); } -TEST_F(PrintToJsonTest, ExpandVariableWsp) { - auto node1_sym = GetSymbol("node1"); +TYPED_TEST(PrintToJsonTest, ExpandVariableWsp) { + auto node1_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<ExpandVariable>( - last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, + last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, EdgeAtom::Direction::OUT, - std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1"), dba.NameToEdgeType("EdgeType2")}, - false, LITERAL(2), LITERAL(5), false, ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"), nullptr}, - ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"), - PROPERTY_LOOKUP("inner_edge", dba.NameToProperty("weight"))}, - GetSymbol("total")); + std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, + false, LITERAL(2), LITERAL(5), false, + ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"), nullptr}, + ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"), + PROPERTY_LOOKUP(this->dba, "inner_edge", this->dba.NameToProperty("weight"))}, + this->GetSymbol("total")); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "ExpandVariable", "input_symbol" : "node1", @@ -330,12 +352,12 @@ TEST_F(PrintToJsonTest, ExpandVariableWsp) { })sep"); } -TEST_F(PrintToJsonTest, ConstructNamedPath) { - auto node1_sym = GetSymbol("node1"); - auto edge1_sym = GetSymbol("edge1"); - auto node2_sym = GetSymbol("node2"); - auto edge2_sym = GetSymbol("edge2"); - auto node3_sym = GetSymbol("node3"); +TYPED_TEST(PrintToJsonTest, ConstructNamedPath) { + auto node1_sym = this->GetSymbol("node1"); + auto edge1_sym = this->GetSymbol("edge1"); + auto node2_sym = this->GetSymbol("node2"); + auto edge2_sym = this->GetSymbol("edge2"); + auto node3_sym = this->GetSymbol("node3"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::OUT, @@ -343,9 +365,9 @@ TEST_F(PrintToJsonTest, ConstructNamedPath) { last_op = std::make_shared<Expand>(last_op, node2_sym, node3_sym, edge2_sym, EdgeAtom::Direction::OUT, std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); last_op = std::make_shared<ConstructNamedPath>( - last_op, GetSymbol("path"), std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym}); + last_op, this->GetSymbol("path"), std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym}); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "ConstructNamedPath", "path_symbol" : "path", @@ -376,12 +398,13 @@ TEST_F(PrintToJsonTest, ConstructNamedPath) { })"); } -TEST_F(PrintToJsonTest, Filter) { - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node1")); - last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(5))); +TYPED_TEST(PrintToJsonTest, Filter) { + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node1")); + last_op = + std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{}, + EQ(PROPERTY_LOOKUP(this->dba, "node1", this->dba.NameToProperty("prop")), LITERAL(5))); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Filter", "expression" : "(== (PropertyLookup (Identifier \"node1\") \"prop\") 5)", @@ -393,11 +416,11 @@ TEST_F(PrintToJsonTest, Filter) { })sep"); } -TEST_F(PrintToJsonTest, Produce) { +TYPED_TEST(PrintToJsonTest, Produce) { std::shared_ptr<LogicalOperator> last_op = std::make_shared<Produce>( nullptr, std::vector<NamedExpression *>{NEXPR("pet", LITERAL(5)), NEXPR("string", LITERAL("string"))}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Produce", "named_expressions" : [ @@ -414,15 +437,15 @@ TEST_F(PrintToJsonTest, Produce) { })sep"); } -TEST_F(PrintToJsonTest, Delete) { - auto node_sym = GetSymbol("node1"); +TYPED_TEST(PrintToJsonTest, Delete) { + auto node_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = - std::make_shared<Expand>(last_op, node_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Direction::BOTH, - std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared<Expand>(last_op, node_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), + EdgeAtom::Direction::BOTH, std::vector<memgraph::storage::EdgeTypeId>{}, false, + memgraph::storage::View::OLD); last_op = std::make_shared<plan::Delete>(last_op, std::vector<Expression *>{IDENT("node2")}, true); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Delete", "expressions" : [ "(Identifier \"node2\")" ], @@ -444,14 +467,14 @@ TEST_F(PrintToJsonTest, Delete) { })sep"); } -TEST_F(PrintToJsonTest, SetProperty) { - memgraph::storage::PropertyId prop = dba.NameToProperty("prop"); +TYPED_TEST(PrintToJsonTest, SetProperty) { + memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop"); - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node")); - last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP("node", prop), - ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1))); + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node")); + last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop), + ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1))); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "SetProperty", "property" : "prop", @@ -465,15 +488,15 @@ TEST_F(PrintToJsonTest, SetProperty) { })sep"); } -TEST_F(PrintToJsonTest, SetProperties) { - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, SetProperties) { + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = std::make_shared<plan::SetProperties>( - last_op, node_sym, - MAP({{storage.GetPropertyIx("prop1"), LITERAL(1)}, {storage.GetPropertyIx("prop2"), LITERAL("propko")}}), - plan::SetProperties::Op::REPLACE); + last_op = std::make_shared<plan::SetProperties>(last_op, node_sym, + MAP({{this->storage.GetPropertyIx("prop1"), LITERAL(1)}, + {this->storage.GetPropertyIx("prop2"), LITERAL("propko")}}), + plan::SetProperties::Op::REPLACE); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "SetProperties", "input_symbol" : "node", @@ -487,13 +510,14 @@ TEST_F(PrintToJsonTest, SetProperties) { })sep"); } -TEST_F(PrintToJsonTest, SetLabels) { - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, SetLabels) { + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); last_op = std::make_shared<plan::SetLabels>( - last_op, node_sym, std::vector<memgraph::storage::LabelId>{dba.NameToLabel("label1"), dba.NameToLabel("label2")}); + last_op, node_sym, + std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "SetLabels", "input_symbol" : "node", @@ -506,13 +530,13 @@ TEST_F(PrintToJsonTest, SetLabels) { })"); } -TEST_F(PrintToJsonTest, RemoveProperty) { - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, RemoveProperty) { + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = std::make_shared<plan::RemoveProperty>(last_op, dba.NameToProperty("prop"), - PROPERTY_LOOKUP("node", dba.NameToProperty("prop"))); + last_op = std::make_shared<plan::RemoveProperty>( + last_op, this->dba.NameToProperty("prop"), PROPERTY_LOOKUP(this->dba, "node", this->dba.NameToProperty("prop"))); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "RemoveProperty", "lhs" : "(PropertyLookup (Identifier \"node\") \"prop\")", @@ -525,13 +549,14 @@ TEST_F(PrintToJsonTest, RemoveProperty) { })sep"); } -TEST_F(PrintToJsonTest, RemoveLabels) { - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, RemoveLabels) { + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); last_op = std::make_shared<plan::RemoveLabels>( - last_op, node_sym, std::vector<memgraph::storage::LabelId>{dba.NameToLabel("label1"), dba.NameToLabel("label2")}); + last_op, node_sym, + std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "RemoveLabels", "input_symbol" : "node", @@ -544,14 +569,14 @@ TEST_F(PrintToJsonTest, RemoveLabels) { })"); } -TEST_F(PrintToJsonTest, EdgeUniquenessFilter) { - auto node1_sym = GetSymbol("node1"); - auto node2_sym = GetSymbol("node2"); - auto node3_sym = GetSymbol("node3"); - auto node4_sym = GetSymbol("node4"); +TYPED_TEST(PrintToJsonTest, EdgeUniquenessFilter) { + auto node1_sym = this->GetSymbol("node1"); + auto node2_sym = this->GetSymbol("node2"); + auto node3_sym = this->GetSymbol("node3"); + auto node4_sym = this->GetSymbol("node4"); - auto edge1_sym = GetSymbol("edge1"); - auto edge2_sym = GetSymbol("edge2"); + auto edge1_sym = this->GetSymbol("edge1"); + auto edge2_sym = this->GetSymbol("edge2"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::IN, @@ -561,7 +586,7 @@ TEST_F(PrintToJsonTest, EdgeUniquenessFilter) { std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); last_op = std::make_shared<EdgeUniquenessFilter>(last_op, edge2_sym, std::vector<Symbol>{edge1_sym}); - Check(last_op.get(), R"( + this->Check(last_op.get(), R"( { "name" : "EdgeUniquenessFilter", "expand_symbol" : "edge2", @@ -596,15 +621,15 @@ TEST_F(PrintToJsonTest, EdgeUniquenessFilter) { })"); } -TEST_F(PrintToJsonTest, Accumulate) { - memgraph::storage::PropertyId prop = dba.NameToProperty("prop"); - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, Accumulate) { + memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop"); + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP("node", prop), - ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1))); + last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop), + ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1))); last_op = std::make_shared<plan::Accumulate>(last_op, std::vector<Symbol>{node_sym}, true); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Accumulate", "symbols" : ["node"], @@ -623,71 +648,72 @@ TEST_F(PrintToJsonTest, Accumulate) { })sep"); } -TEST_F(PrintToJsonTest, Aggregate) { - memgraph::storage::PropertyId value = dba.NameToProperty("value"); - memgraph::storage::PropertyId color = dba.NameToProperty("color"); - memgraph::storage::PropertyId type = dba.NameToProperty("type"); - auto node_sym = GetSymbol("node"); - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = std::make_shared<plan::Aggregate>( - last_op, - std::vector<Aggregate::Element>{{PROPERTY_LOOKUP("node", value), nullptr, Aggregation::Op::SUM, GetSymbol("sum")}, - {PROPERTY_LOOKUP("node", value), PROPERTY_LOOKUP("node", color), - Aggregation::Op::COLLECT_MAP, GetSymbol("map")}, - {nullptr, nullptr, Aggregation::Op::COUNT, GetSymbol("count")}}, - std::vector<Expression *>{PROPERTY_LOOKUP("node", type)}, std::vector<Symbol>{node_sym}); - - Check(last_op.get(), R"sep( - { - "name" : "Aggregate", - "aggregations" : [ - { - "value" : "(PropertyLookup (Identifier \"node\") \"value\")", - "op" : "sum", - "output_symbol" : "sum", - "distinct" : false - }, - { - "value" : "(PropertyLookup (Identifier \"node\") \"value\")", - "key" : "(PropertyLookup (Identifier \"node\") \"color\")", - "op" : "collect", - "output_symbol" : "map", - "distinct" : false - }, - { - "op": "count", - "output_symbol": "count", - "distinct" : false - } - ], - "group_by" : [ - "(PropertyLookup (Identifier \"node\") \"type\")" - ], - "remember" : ["node"], - "input" : { - "name" : "ScanAll", - "output_symbol" : "node", - "input" : { "name" : "Once" } - } - })sep"); -} - -TEST_F(PrintToJsonTest, AggregateWithDistinct) { - memgraph::storage::PropertyId value = dba.NameToProperty("value"); - memgraph::storage::PropertyId color = dba.NameToProperty("color"); - memgraph::storage::PropertyId type = dba.NameToProperty("type"); - auto node_sym = GetSymbol("node"); +TYPED_TEST(PrintToJsonTest, Aggregate) { + memgraph::storage::PropertyId value = this->dba.NameToProperty("value"); + memgraph::storage::PropertyId color = this->dba.NameToProperty("color"); + memgraph::storage::PropertyId type = this->dba.NameToProperty("type"); + auto node_sym = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); last_op = std::make_shared<plan::Aggregate>( last_op, std::vector<Aggregate::Element>{ - {PROPERTY_LOOKUP("node", value), nullptr, Aggregation::Op::SUM, GetSymbol("sum"), true}, - {PROPERTY_LOOKUP("node", value), PROPERTY_LOOKUP("node", color), Aggregation::Op::COLLECT_MAP, - GetSymbol("map"), true}, - {nullptr, nullptr, Aggregation::Op::COUNT, GetSymbol("count"), true}}, - std::vector<Expression *>{PROPERTY_LOOKUP("node", type)}, std::vector<Symbol>{node_sym}); + {PROPERTY_LOOKUP(this->dba, "node", value), nullptr, Aggregation::Op::SUM, this->GetSymbol("sum")}, + {PROPERTY_LOOKUP(this->dba, "node", value), PROPERTY_LOOKUP(this->dba, "node", color), + Aggregation::Op::COLLECT_MAP, this->GetSymbol("map")}, + {nullptr, nullptr, Aggregation::Op::COUNT, this->GetSymbol("count")}}, + std::vector<Expression *>{PROPERTY_LOOKUP(this->dba, "node", type)}, std::vector<Symbol>{node_sym}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( + { + "name" : "Aggregate", + "aggregations" : [ + { + "value" : "(PropertyLookup (Identifier \"node\") \"value\")", + "op" : "sum", + "output_symbol" : "sum", + "distinct" : false + }, + { + "value" : "(PropertyLookup (Identifier \"node\") \"value\")", + "key" : "(PropertyLookup (Identifier \"node\") \"color\")", + "op" : "collect", + "output_symbol" : "map", + "distinct" : false + }, + { + "op": "count", + "output_symbol": "count", + "distinct" : false + } + ], + "group_by" : [ + "(PropertyLookup (Identifier \"node\") \"type\")" + ], + "remember" : ["node"], + "input" : { + "name" : "ScanAll", + "output_symbol" : "node", + "input" : { "name" : "Once" } + } + })sep"); +} + +TYPED_TEST(PrintToJsonTest, AggregateWithDistinct) { + memgraph::storage::PropertyId value = this->dba.NameToProperty("value"); + memgraph::storage::PropertyId color = this->dba.NameToProperty("color"); + memgraph::storage::PropertyId type = this->dba.NameToProperty("type"); + auto node_sym = this->GetSymbol("node"); + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); + last_op = std::make_shared<plan::Aggregate>( + last_op, + std::vector<Aggregate::Element>{ + {PROPERTY_LOOKUP(this->dba, "node", value), nullptr, Aggregation::Op::SUM, this->GetSymbol("sum"), true}, + {PROPERTY_LOOKUP(this->dba, "node", value), PROPERTY_LOOKUP(this->dba, "node", color), + Aggregation::Op::COLLECT_MAP, this->GetSymbol("map"), true}, + {nullptr, nullptr, Aggregation::Op::COUNT, this->GetSymbol("count"), true}}, + std::vector<Expression *>{PROPERTY_LOOKUP(this->dba, "node", type)}, std::vector<Symbol>{node_sym}); + + this->Check(last_op.get(), R"sep( { "name" : "Aggregate", "aggregations" : [ @@ -722,11 +748,11 @@ TEST_F(PrintToJsonTest, AggregateWithDistinct) { })sep"); } -TEST_F(PrintToJsonTest, Skip) { - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node")); +TYPED_TEST(PrintToJsonTest, Skip) { + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node")); last_op = std::make_shared<Skip>(last_op, LITERAL(42)); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Skip", "expression" : "42", @@ -738,11 +764,11 @@ TEST_F(PrintToJsonTest, Skip) { })sep"); } -TEST_F(PrintToJsonTest, Limit) { - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node")); +TYPED_TEST(PrintToJsonTest, Limit) { + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node")); last_op = std::make_shared<Limit>(last_op, LITERAL(42)); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Limit", "expression" : "42", @@ -754,17 +780,18 @@ TEST_F(PrintToJsonTest, Limit) { })sep"); } -TEST_F(PrintToJsonTest, OrderBy) { - Symbol node_sym = GetSymbol("node"); - memgraph::storage::PropertyId value = dba.NameToProperty("value"); - memgraph::storage::PropertyId color = dba.NameToProperty("color"); +TYPED_TEST(PrintToJsonTest, OrderBy) { + Symbol node_sym = this->GetSymbol("node"); + memgraph::storage::PropertyId value = this->dba.NameToProperty("value"); + memgraph::storage::PropertyId color = this->dba.NameToProperty("color"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = std::make_shared<OrderBy>(last_op, - std::vector<SortItem>{{Ordering::ASC, PROPERTY_LOOKUP("node", value)}, - {Ordering::DESC, PROPERTY_LOOKUP("node", color)}}, - std::vector<Symbol>{node_sym}); + last_op = + std::make_shared<OrderBy>(last_op, + std::vector<SortItem>{{Ordering::ASC, PROPERTY_LOOKUP(this->dba, "node", value)}, + {Ordering::DESC, PROPERTY_LOOKUP(this->dba, "node", color)}}, + std::vector<Symbol>{node_sym}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "OrderBy", "order_by" : [ @@ -786,9 +813,9 @@ TEST_F(PrintToJsonTest, OrderBy) { })sep"); } -TEST_F(PrintToJsonTest, Merge) { - Symbol node_sym = GetSymbol("node"); - memgraph::storage::LabelId label = dba.NameToLabel("label"); +TYPED_TEST(PrintToJsonTest, Merge) { + Symbol node_sym = this->GetSymbol("node"); + memgraph::storage::LabelId label = this->dba.NameToLabel("label"); std::shared_ptr<LogicalOperator> match = std::make_shared<ScanAllByLabel>(nullptr, node_sym, label); @@ -797,7 +824,7 @@ TEST_F(PrintToJsonTest, Merge) { std::shared_ptr<LogicalOperator> last_op = std::make_shared<plan::Merge>(nullptr, match, create); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Merge", "input" : { "name" : "Once" }, @@ -819,10 +846,10 @@ TEST_F(PrintToJsonTest, Merge) { })sep"); } -TEST_F(PrintToJsonTest, Optional) { - Symbol node1_sym = GetSymbol("node1"); - Symbol node2_sym = GetSymbol("node2"); - Symbol edge_sym = GetSymbol("edge"); +TYPED_TEST(PrintToJsonTest, Optional) { + Symbol node1_sym = this->GetSymbol("node1"); + Symbol node2_sym = this->GetSymbol("node2"); + Symbol edge_sym = this->GetSymbol("edge"); std::shared_ptr<LogicalOperator> input = std::make_shared<ScanAll>(nullptr, node1_sym); @@ -833,7 +860,7 @@ TEST_F(PrintToJsonTest, Optional) { std::shared_ptr<LogicalOperator> last_op = std::make_shared<Optional>(input, expand, std::vector<Symbol>{node2_sym, edge_sym}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Optional", "input" : { @@ -855,11 +882,11 @@ TEST_F(PrintToJsonTest, Optional) { })sep"); } -TEST_F(PrintToJsonTest, Unwind) { +TYPED_TEST(PrintToJsonTest, Unwind) { std::shared_ptr<LogicalOperator> last_op = - std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), GetSymbol("x")); + std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), this->GetSymbol("x")); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Unwind", "output_symbol" : "x", @@ -868,13 +895,13 @@ TEST_F(PrintToJsonTest, Unwind) { })sep"); } -TEST_F(PrintToJsonTest, Distinct) { - Symbol x = GetSymbol("x"); +TYPED_TEST(PrintToJsonTest, Distinct) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); last_op = std::make_shared<Distinct>(last_op, std::vector<Symbol>{x}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Distinct", "value_symbols" : ["x"], @@ -887,18 +914,18 @@ TEST_F(PrintToJsonTest, Distinct) { })sep"); } -TEST_F(PrintToJsonTest, Union) { - Symbol x = GetSymbol("x"); +TYPED_TEST(PrintToJsonTest, Union) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); - Symbol node = GetSymbol("x"); + Symbol node = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> rhs = std::make_shared<ScanAll>(nullptr, node); - std::shared_ptr<LogicalOperator> last_op = std::make_shared<Union>(lhs, rhs, std::vector<Symbol>{GetSymbol("x")}, - std::vector<Symbol>{x}, std::vector<Symbol>{node}); + std::shared_ptr<LogicalOperator> last_op = std::make_shared<Union>( + lhs, rhs, std::vector<Symbol>{this->GetSymbol("x")}, std::vector<Symbol>{x}, std::vector<Symbol>{node}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Union", "union_symbols" : ["x"], @@ -918,18 +945,18 @@ TEST_F(PrintToJsonTest, Union) { })sep"); } -TEST_F(PrintToJsonTest, Cartesian) { - Symbol x = GetSymbol("x"); +TYPED_TEST(PrintToJsonTest, Cartesian) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); - Symbol node = GetSymbol("node"); + Symbol node = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> rhs = std::make_shared<ScanAll>(nullptr, node); std::shared_ptr<LogicalOperator> last_op = std::make_shared<Cartesian>(lhs, std::vector<Symbol>{x}, rhs, std::vector<Symbol>{node}); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "name" : "Cartesian", "left_symbols" : ["x"], @@ -948,14 +975,14 @@ TEST_F(PrintToJsonTest, Cartesian) { })sep"); } -TEST_F(PrintToJsonTest, CallProcedure) { +TYPED_TEST(PrintToJsonTest, CallProcedure) { memgraph::query::plan::CallProcedure call_op; call_op.input_ = std::make_shared<Once>(); call_op.procedure_name_ = "mg.reload"; call_op.arguments_ = {LITERAL("example")}; call_op.result_fields_ = {"name", "signature"}; - call_op.result_symbols_ = {GetSymbol("name_alias"), GetSymbol("signature_alias")}; - Check(&call_op, R"sep( + call_op.result_symbols_ = {this->GetSymbol("name_alias"), this->GetSymbol("signature_alias")}; + this->Check(&call_op, R"sep( { "arguments" : ["\"example\""], "input" : { "name" : "Once" }, @@ -966,14 +993,14 @@ TEST_F(PrintToJsonTest, CallProcedure) { })sep"); } -TEST_F(PrintToJsonTest, Foreach) { - Symbol x = GetSymbol("x"); - std::shared_ptr<LogicalOperator> create = - std::make_shared<CreateNode>(nullptr, NodeCreationInfo{GetSymbol("node"), {dba.NameToLabel("Label1")}, {}}); +TYPED_TEST(PrintToJsonTest, Foreach) { + Symbol x = this->GetSymbol("x"); + std::shared_ptr<LogicalOperator> create = std::make_shared<CreateNode>( + nullptr, NodeCreationInfo{this->GetSymbol("node"), {this->dba.NameToLabel("Label1")}, {}}); std::shared_ptr<LogicalOperator> foreach = std::make_shared<plan::Foreach>(nullptr, std::move(create), LIST(LITERAL(1)), x); - Check(foreach.get(), R"sep( + this->Check(foreach.get(), R"sep( { "expression": "(ListLiteral [1])", "input": { @@ -997,15 +1024,16 @@ TEST_F(PrintToJsonTest, Foreach) { })sep"); } -TEST_F(PrintToJsonTest, Exists) { - Symbol x = GetSymbol("x"); - Symbol e = GetSymbol("edge"); - Symbol n = GetSymbol("node"); - Symbol output = GetSymbol("output_symbol"); +TYPED_TEST(PrintToJsonTest, Exists) { + Symbol x = this->GetSymbol("x"); + Symbol e = this->GetSymbol("edge"); + Symbol n = this->GetSymbol("node"); + Symbol output = this->GetSymbol("output_symbol"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, x); - std::shared_ptr<LogicalOperator> expand = std::make_shared<Expand>( - nullptr, x, n, e, memgraph::query::EdgeAtom::Direction::BOTH, - std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1")}, false, memgraph::storage::View::OLD); + std::shared_ptr<LogicalOperator> expand = + std::make_shared<Expand>(nullptr, x, n, e, memgraph::query::EdgeAtom::Direction::BOTH, + std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1")}, false, + memgraph::storage::View::OLD); std::shared_ptr<LogicalOperator> limit = std::make_shared<Limit>(expand, LITERAL(1)); std::shared_ptr<LogicalOperator> evaluate_pattern_filter = std::make_shared<EvaluatePatternFilter>(limit, output); last_op = std::make_shared<Filter>( @@ -1013,7 +1041,7 @@ TEST_F(PrintToJsonTest, Exists) { EXISTS(PATTERN(NODE("x"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {}, false), NODE("node", std::nullopt, false)))); - Check(last_op.get(), R"sep( + this->Check(last_op.get(), R"sep( { "expression": "(Exists expression)", "input": { diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 444053e01..a01d21f7c 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -509,52 +509,55 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec /// AstStorage storage; /// auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), /// RETURN(NEXPR("new_name"), IDENT("m"))); -#define NODE(...) memgraph::query::test_common::GetNode(storage, __VA_ARGS__) -#define EDGE(...) memgraph::query::test_common::GetEdge(storage, __VA_ARGS__) -#define EDGE_VARIABLE(...) memgraph::query::test_common::GetEdgeVariable(storage, __VA_ARGS__) -#define PATTERN(...) memgraph::query::test_common::GetPattern(storage, {__VA_ARGS__}) -#define NAMED_PATTERN(name, ...) memgraph::query::test_common::GetPattern(storage, name, {__VA_ARGS__}) -#define OPTIONAL_MATCH(...) \ - memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Match>(true), {__VA_ARGS__}) +#define NODE(...) memgraph::query::test_common::GetNode(this->storage, __VA_ARGS__) +#define EDGE(...) memgraph::query::test_common::GetEdge(this->storage, __VA_ARGS__) +#define EDGE_VARIABLE(...) memgraph::query::test_common::GetEdgeVariable(this->storage, __VA_ARGS__) +#define PATTERN(...) memgraph::query::test_common::GetPattern(this->storage, {__VA_ARGS__}) +#define NAMED_PATTERN(name, ...) memgraph::query::test_common::GetPattern(this->storage, name, {__VA_ARGS__}) +#define OPTIONAL_MATCH(...) \ + memgraph::query::test_common::GetWithPatterns(this->storage.template Create<memgraph::query::Match>(true), \ + {__VA_ARGS__}) #define MATCH(...) \ - memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Match>(), {__VA_ARGS__}) -#define WHERE(expr) storage.Create<memgraph::query::Where>((expr)) + memgraph::query::test_common::GetWithPatterns(this->storage.template Create<memgraph::query::Match>(), {__VA_ARGS__}) +#define WHERE(expr) this->storage.template Create<memgraph::query::Where>((expr)) #define CREATE(...) \ - memgraph::query::test_common::GetWithPatterns(storage.Create<memgraph::query::Create>(), {__VA_ARGS__}) -#define IDENT(...) storage.Create<memgraph::query::Identifier>(__VA_ARGS__) -#define LITERAL(val) storage.Create<memgraph::query::PrimitiveLiteral>((val)) -#define LIST(...) storage.Create<memgraph::query::ListLiteral>(std::vector<memgraph::query::Expression *>{__VA_ARGS__}) -#define MAP(...) \ - storage.Create<memgraph::query::MapLiteral>( \ + memgraph::query::test_common::GetWithPatterns(this->storage.template Create<memgraph::query::Create>(), {__VA_ARGS__}) +#define IDENT(...) this->storage.template Create<memgraph::query::Identifier>(__VA_ARGS__) +#define LITERAL(val) this->storage.template Create<memgraph::query::PrimitiveLiteral>((val)) +#define LIST(...) \ + this->storage.template Create<memgraph::query::ListLiteral>(std::vector<memgraph::query::Expression *>{__VA_ARGS__}) +#define MAP(...) \ + this->storage.template Create<memgraph::query::MapLiteral>( \ std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{__VA_ARGS__}) -#define MAP_PROJECTION(map_variable, elements) \ - storage.Create<memgraph::query::MapProjectionLiteral>( \ - (memgraph::query::Expression *){map_variable}, \ +#define PROPERTY_PAIR(dba, property_name) std::make_pair(property_name, dba.NameToProperty(property_name)) +#define PROPERTY_LOOKUP(dba, ...) memgraph::query::test_common::GetPropertyLookup(this->storage, dba, __VA_ARGS__) +#define PARAMETER_LOOKUP(token_position) \ + this->storage.template Create<memgraph::query::ParameterLookup>((token_position)) +#define NEXPR(name, expr) this->storage.template Create<memgraph::query::NamedExpression>((name), (expr)) +#define MAP_PROJECTION(map_variable, elements) \ + this->storage.template Create<memgraph::query::MapProjectionLiteral>( \ + (memgraph::query::Expression *){map_variable}, \ std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{elements}) -#define PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToProperty(property_name)) -#define PROPERTY_LOOKUP(...) memgraph::query::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__) -#define ALL_PROPERTIES_LOOKUP(expr) memgraph::query::test_common::GetAllPropertiesLookup(storage, expr) -#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::ParameterLookup>((token_position)) -#define NEXPR(name, expr) storage.Create<memgraph::query::NamedExpression>((name), (expr)) +#define ALL_PROPERTIES_LOOKUP(expr) memgraph::query::test_common::GetAllPropertiesLookup(this->storage, expr) // AS is alternative to NEXPR which does not initialize NamedExpression with // Expression. It should be used with RETURN or WITH. For example: // RETURN(IDENT("n"), AS("n")) vs. RETURN(NEXPR("n", IDENT("n"))). -#define AS(name) storage.Create<memgraph::query::NamedExpression>((name)) -#define RETURN(...) memgraph::query::test_common::GetReturn(storage, false, __VA_ARGS__) -#define WITH(...) memgraph::query::test_common::GetWith(storage, false, __VA_ARGS__) -#define RETURN_DISTINCT(...) memgraph::query::test_common::GetReturn(storage, true, __VA_ARGS__) -#define WITH_DISTINCT(...) memgraph::query::test_common::GetWith(storage, true, __VA_ARGS__) -#define UNWIND(...) memgraph::query::test_common::GetUnwind(storage, __VA_ARGS__) +#define AS(name) this->storage.template Create<memgraph::query::NamedExpression>((name)) +#define RETURN(...) memgraph::query::test_common::GetReturn(this->storage, false, __VA_ARGS__) +#define WITH(...) memgraph::query::test_common::GetWith(this->storage, false, __VA_ARGS__) +#define RETURN_DISTINCT(...) memgraph::query::test_common::GetReturn(this->storage, true, __VA_ARGS__) +#define WITH_DISTINCT(...) memgraph::query::test_common::GetWith(this->storage, true, __VA_ARGS__) +#define UNWIND(...) memgraph::query::test_common::GetUnwind(this->storage, __VA_ARGS__) #define ORDER_BY(...) memgraph::query::test_common::GetOrderBy(__VA_ARGS__) #define SKIP(expr) \ memgraph::query::test_common::Skip { (expr) } #define LIMIT(expr) \ memgraph::query::test_common::Limit { (expr) } -#define DELETE(...) memgraph::query::test_common::GetDelete(storage, {__VA_ARGS__}) +#define DELETE(...) memgraph::query::test_common::GetDelete(this->storage, {__VA_ARGS__}) #define DETACH_DELETE(...) memgraph::query::test_common::GetDelete(storage, {__VA_ARGS__}, true) -#define SET(...) memgraph::query::test_common::GetSet(storage, __VA_ARGS__) -#define REMOVE(...) memgraph::query::test_common::GetRemove(storage, __VA_ARGS__) -#define MERGE(...) memgraph::query::test_common::GetMerge(storage, __VA_ARGS__) +#define SET(...) memgraph::query::test_common::GetSet(this->storage, __VA_ARGS__) +#define REMOVE(...) memgraph::query::test_common::GetRemove(this->storage, __VA_ARGS__) +#define MERGE(...) memgraph::query::test_common::GetMerge(this->storage, __VA_ARGS__) #define ON_MATCH(...) \ memgraph::query::test_common::OnMatch { \ std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \ @@ -566,62 +569,72 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec #define CREATE_INDEX_ON(label, property) \ storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \ std::vector<memgraph::query::PropertyIx>{(property)}) -#define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__) -#define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__) -#define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__) -#define UNION_ALL(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), __VA_ARGS__) -#define FOREACH(...) memgraph::query::test_common::GetForeach(storage, __VA_ARGS__) +#define QUERY(...) memgraph::query::test_common::GetQuery(this->storage, __VA_ARGS__) +#define SINGLE_QUERY(...) \ + memgraph::query::test_common::GetSingleQuery(this->storage.template Create<SingleQuery>(), __VA_ARGS__) +#define UNION(...) \ + memgraph::query::test_common::GetCypherUnion(this->storage.template Create<CypherUnion>(true), __VA_ARGS__) +#define UNION_ALL(...) \ + memgraph::query::test_common::GetCypherUnion(this->storage.template Create<CypherUnion>(false), __VA_ARGS__) +#define FOREACH(...) memgraph::query::test_common::GetForeach(this->storage, __VA_ARGS__) // Various operators -#define NOT(expr) storage.Create<memgraph::query::NotOperator>((expr)) -#define UPLUS(expr) storage.Create<memgraph::query::UnaryPlusOperator>((expr)) -#define UMINUS(expr) storage.Create<memgraph::query::UnaryMinusOperator>((expr)) -#define IS_NULL(expr) storage.Create<memgraph::query::IsNullOperator>((expr)) -#define ADD(expr1, expr2) storage.Create<memgraph::query::AdditionOperator>((expr1), (expr2)) -#define LESS(expr1, expr2) storage.Create<memgraph::query::LessOperator>((expr1), (expr2)) -#define LESS_EQ(expr1, expr2) storage.Create<memgraph::query::LessEqualOperator>((expr1), (expr2)) -#define GREATER(expr1, expr2) storage.Create<memgraph::query::GreaterOperator>((expr1), (expr2)) -#define GREATER_EQ(expr1, expr2) storage.Create<memgraph::query::GreaterEqualOperator>((expr1), (expr2)) -#define SUM(expr, distinct) \ - storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::SUM, (distinct)) -#define COUNT(expr, distinct) \ - storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COUNT, (distinct)) +#define NOT(expr) this->storage.template Create<memgraph::query::NotOperator>((expr)) +#define UPLUS(expr) this->storage.template Create<memgraph::query::UnaryPlusOperator>((expr)) +#define UMINUS(expr) this->storage.template Create<memgraph::query::UnaryMinusOperator>((expr)) +#define IS_NULL(expr) this->storage.template Create<memgraph::query::IsNullOperator>((expr)) +#define ADD(expr1, expr2) this->storage.template Create<memgraph::query::AdditionOperator>((expr1), (expr2)) +#define LESS(expr1, expr2) this->storage.template Create<memgraph::query::LessOperator>((expr1), (expr2)) +#define LESS_EQ(expr1, expr2) this->storage.template Create<memgraph::query::LessEqualOperator>((expr1), (expr2)) +#define GREATER(expr1, expr2) this->storage.template Create<memgraph::query::GreaterOperator>((expr1), (expr2)) +#define GREATER_EQ(expr1, expr2) this->storage.template Create<memgraph::query::GreaterEqualOperator>((expr1), (expr2)) +#define SUM(expr, distinct) \ + this->storage.template Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::SUM, \ + (distinct)) +#define COUNT(expr, distinct) \ + this->storage.template Create<memgraph::query::Aggregation>((expr), nullptr, \ + memgraph::query::Aggregation::Op::COUNT, (distinct)) #define AVG(expr, distinct) \ storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::AVG, (distinct)) #define COLLECT_LIST(expr, distinct) \ storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COLLECT_LIST, \ (distinct)) -#define EQ(expr1, expr2) storage.Create<memgraph::query::EqualOperator>((expr1), (expr2)) -#define NEQ(expr1, expr2) storage.Create<memgraph::query::NotEqualOperator>((expr1), (expr2)) -#define AND(expr1, expr2) storage.Create<memgraph::query::AndOperator>((expr1), (expr2)) -#define OR(expr1, expr2) storage.Create<memgraph::query::OrOperator>((expr1), (expr2)) -#define IN_LIST(expr1, expr2) storage.Create<memgraph::query::InListOperator>((expr1), (expr2)) +#define EQ(expr1, expr2) this->storage.template Create<memgraph::query::EqualOperator>((expr1), (expr2)) +#define NEQ(expr1, expr2) this->storage.template Create<memgraph::query::NotEqualOperator>((expr1), (expr2)) +#define AND(expr1, expr2) this->storage.template Create<memgraph::query::AndOperator>((expr1), (expr2)) +#define OR(expr1, expr2) this->storage.template Create<memgraph::query::OrOperator>((expr1), (expr2)) +#define IN_LIST(expr1, expr2) this->storage.template Create<memgraph::query::InListOperator>((expr1), (expr2)) #define IF(cond, then, else) storage.Create<memgraph::query::IfOperator>((cond), (then), (else)) // Function call -#define FN(function_name, ...) \ - storage.Create<memgraph::query::Function>(memgraph::utils::ToUpperCase(function_name), \ - std::vector<memgraph::query::Expression *>{__VA_ARGS__}) +#define FN(function_name, ...) \ + this->storage.template Create<memgraph::query::Function>(memgraph::utils::ToUpperCase(function_name), \ + std::vector<memgraph::query::Expression *>{__VA_ARGS__}) // List slicing #define SLICE(list, lower_bound, upper_bound) \ - storage.Create<memgraph::query::ListSlicingOperator>(list, lower_bound, upper_bound) + this->storage.template Create<memgraph::query::ListSlicingOperator>(list, lower_bound, upper_bound) // all(variable IN list WHERE predicate) -#define ALL(variable, list, where) \ - storage.Create<memgraph::query::All>(storage.Create<memgraph::query::Identifier>(variable), list, where) -#define SINGLE(variable, list, where) \ - storage.Create<memgraph::query::Single>(storage.Create<memgraph::query::Identifier>(variable), list, where) +#define ALL(variable, list, where) \ + this->storage.template Create<memgraph::query::All>( \ + this->storage.template Create<memgraph::query::Identifier>(variable), list, where) +#define SINGLE(variable, list, where) \ + this->storage.template Create<memgraph::query::Single>( \ + this->storage.template Create<memgraph::query::Identifier>(variable), list, where) #define ANY(variable, list, where) \ storage.Create<memgraph::query::Any>(storage.Create<memgraph::query::Identifier>(variable), list, where) #define NONE(variable, list, where) \ storage.Create<memgraph::query::None>(storage.Create<memgraph::query::Identifier>(variable), list, where) -#define REDUCE(accumulator, initializer, variable, list, expr) \ - storage.Create<memgraph::query::Reduce>(storage.Create<memgraph::query::Identifier>(accumulator), initializer, \ - storage.Create<memgraph::query::Identifier>(variable), list, expr) -#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__}) -#define EXTRACT(variable, list, expr) \ - storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr) -#define EXISTS(pattern) storage.Create<memgraph::query::Exists>(pattern) +#define REDUCE(accumulator, initializer, variable, list, expr) \ + this->storage.template Create<memgraph::query::Reduce>( \ + this->storage.template Create<memgraph::query::Identifier>(accumulator), initializer, \ + this->storage.template Create<memgraph::query::Identifier>(variable), list, expr) +#define COALESCE(...) \ + this->storage.template Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__}) +#define EXTRACT(variable, list, expr) \ + this->storage.template Create<memgraph::query::Extract>( \ + this->storage.template Create<memgraph::query::Identifier>(variable), list, expr) +#define EXISTS(pattern) this->storage.template Create<memgraph::query::Exists>(pattern) #define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \ storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \ (labels), (edgeTypes)) #define DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames)) #define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__) -#define CALL_SUBQUERY(...) memgraph::query::test_common::GetCallSubquery(storage, __VA_ARGS__) +#define CALL_SUBQUERY(...) memgraph::query::test_common::GetCallSubquery(this->storage, __VA_ARGS__) diff --git a/tests/unit/query_cost_estimator.cpp b/tests/unit/query_cost_estimator.cpp index d89031b82..f089e6cba 100644 --- a/tests/unit/query_cost_estimator.cpp +++ b/tests/unit/query_cost_estimator.cpp @@ -17,6 +17,7 @@ #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/cost_estimator.hpp" #include "query/plan/operator.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" using namespace memgraph::query; @@ -33,11 +34,11 @@ using MiscParam = CostEstimator<memgraph::query::DbAccessor>::MiscParam; * estimation testing. */ class QueryCostEstimator : public ::testing::Test { protected: - memgraph::storage::Storage db; - std::optional<memgraph::storage::Storage::Accessor> storage_dba; + std::unique_ptr<memgraph::storage::Storage> db = std::make_unique<memgraph::storage::InMemoryStorage>(); + std::optional<std::unique_ptr<memgraph::storage::Storage::Accessor>> storage_dba; std::optional<memgraph::query::DbAccessor> dba; - memgraph::storage::LabelId label = db.NameToLabel("label"); - memgraph::storage::PropertyId property = db.NameToProperty("property"); + memgraph::storage::LabelId label = db->NameToLabel("label"); + memgraph::storage::PropertyId property = db->NameToProperty("property"); // we incrementally build the logical operator plan // start it off with Once @@ -49,10 +50,10 @@ class QueryCostEstimator : public ::testing::Test { int symbol_count = 0; void SetUp() { - ASSERT_FALSE(db.CreateIndex(label).HasError()); - ASSERT_FALSE(db.CreateIndex(label, property).HasError()); - storage_dba.emplace(db.Access()); - dba.emplace(&*storage_dba); + ASSERT_FALSE(db->CreateIndex(label).HasError()); + ASSERT_FALSE(db->CreateIndex(label, property).HasError()); + storage_dba.emplace(db->Access()); + dba.emplace(storage_dba->get()); } Symbol NextSymbol() { return symbol_table_.CreateSymbol("Symbol" + std::to_string(symbol_count++), true); } diff --git a/tests/unit/query_dump.cpp b/tests/unit/query_dump.cpp index c3781834c..6c992c38f 100644 --- a/tests/unit/query_dump.cpp +++ b/tests/unit/query_dump.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,10 +17,14 @@ #include <vector> #include "communication/result_stream_faker.hpp" +#include "disk_test_utils.hpp" #include "query/config.hpp" #include "query/dump.hpp" #include "query/interpreter.hpp" #include "query/typed_value.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/temporal.hpp" #include "utils/temporal.hpp" @@ -130,18 +134,18 @@ DatabaseState GetState(memgraph::storage::Storage *db) { std::map<memgraph::storage::Gid, int64_t> gid_mapping; std::set<DatabaseState::Vertex> vertices; auto dba = db->Access(); - for (const auto &vertex : dba.Vertices(memgraph::storage::View::NEW)) { + for (const auto &vertex : dba->Vertices(memgraph::storage::View::NEW)) { std::set<std::string> labels; auto maybe_labels = vertex.Labels(memgraph::storage::View::NEW); MG_ASSERT(maybe_labels.HasValue()); for (const auto &label : *maybe_labels) { - labels.insert(dba.LabelToName(label)); + labels.insert(dba->LabelToName(label)); } std::map<std::string, memgraph::storage::PropertyValue> props; auto maybe_properties = vertex.Properties(memgraph::storage::View::NEW); MG_ASSERT(maybe_properties.HasValue()); for (const auto &kv : *maybe_properties) { - props.emplace(dba.PropertyToName(kv.first), kv.second); + props.emplace(dba->PropertyToName(kv.first), kv.second); } MG_ASSERT(props.count(kPropertyId) == 1); const auto id = props[kPropertyId].ValueInt(); @@ -151,16 +155,16 @@ DatabaseState GetState(memgraph::storage::Storage *db) { // Capture all edges std::set<DatabaseState::Edge> edges; - for (const auto &vertex : dba.Vertices(memgraph::storage::View::NEW)) { + for (const auto &vertex : dba->Vertices(memgraph::storage::View::NEW)) { auto maybe_edges = vertex.OutEdges(memgraph::storage::View::NEW); MG_ASSERT(maybe_edges.HasValue()); for (const auto &edge : *maybe_edges) { - const auto &edge_type_name = dba.EdgeTypeToName(edge.EdgeType()); + const auto &edge_type_name = dba->EdgeTypeToName(edge.EdgeType()); std::map<std::string, memgraph::storage::PropertyValue> props; auto maybe_properties = edge.Properties(memgraph::storage::View::NEW); MG_ASSERT(maybe_properties.HasValue()); for (const auto &kv : *maybe_properties) { - props.emplace(dba.PropertyToName(kv.first), kv.second); + props.emplace(dba->PropertyToName(kv.first), kv.second); } const auto from = gid_mapping[edge.FromVertex().Gid()]; const auto to = gid_mapping[edge.ToVertex().Gid()]; @@ -172,12 +176,12 @@ DatabaseState GetState(memgraph::storage::Storage *db) { std::set<DatabaseState::LabelItem> label_indices; std::set<DatabaseState::LabelPropertyItem> label_property_indices; { - auto info = dba.ListAllIndices(); + auto info = dba->ListAllIndices(); for (const auto &item : info.label) { - label_indices.insert({dba.LabelToName(item)}); + label_indices.insert({dba->LabelToName(item)}); } for (const auto &item : info.label_property) { - label_property_indices.insert({dba.LabelToName(item.first), dba.PropertyToName(item.second)}); + label_property_indices.insert({dba->LabelToName(item.first), dba->PropertyToName(item.second)}); } } @@ -185,27 +189,25 @@ DatabaseState GetState(memgraph::storage::Storage *db) { std::set<DatabaseState::LabelPropertyItem> existence_constraints; std::set<DatabaseState::LabelPropertiesItem> unique_constraints; { - auto info = dba.ListAllConstraints(); + auto info = dba->ListAllConstraints(); for (const auto &item : info.existence) { - existence_constraints.insert({dba.LabelToName(item.first), dba.PropertyToName(item.second)}); + existence_constraints.insert({dba->LabelToName(item.first), dba->PropertyToName(item.second)}); } for (const auto &item : info.unique) { std::set<std::string> properties; for (const auto &property : item.second) { - properties.insert(dba.PropertyToName(property)); + properties.insert(dba->PropertyToName(property)); } - unique_constraints.insert({dba.LabelToName(item.first), std::move(properties)}); + unique_constraints.insert({dba->LabelToName(item.first), std::move(properties)}); } } return {vertices, edges, label_indices, label_property_indices, existence_constraints, unique_constraints}; } -auto Execute(memgraph::storage::Storage *db, const std::string &query) { - auto data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_query_dump"; - memgraph::query::InterpreterContext context(db, memgraph::query::InterpreterConfig{}, data_directory); - memgraph::query::Interpreter interpreter(&context); - ResultStreamFaker stream(db); +auto Execute(memgraph::query::InterpreterContext *context, const std::string &query) { + memgraph::query::Interpreter interpreter(context); + ResultStreamFaker stream(context->db.get()); auto [header, _, qid] = interpreter.Prepare(query, {}, nullptr); stream.Header(header); @@ -243,14 +245,16 @@ memgraph::storage::EdgeAccessor CreateEdge(memgraph::storage::Storage::Accessor MG_ASSERT(dba); auto edge = dba->CreateEdge(from, to, dba->NameToEdgeType(edge_type_name)); MG_ASSERT(edge.HasValue()); + auto edgeAcc = std::move(edge.GetValue()); for (const auto &kv : props) { - MG_ASSERT(edge->SetProperty(dba->NameToProperty(kv.first), kv.second).HasValue()); + MG_ASSERT(edgeAcc.SetProperty(dba->NameToProperty(kv.first), kv.second).HasValue()); } if (add_property_id) { - MG_ASSERT(edge->SetProperty(dba->NameToProperty(kPropertyId), memgraph::storage::PropertyValue(edge->Gid().AsInt())) - .HasValue()); + MG_ASSERT( + edgeAcc.SetProperty(dba->NameToProperty(kPropertyId), memgraph::storage::PropertyValue(edgeAcc.Gid().AsInt())) + .HasValue()); } - return *edge; + return edgeAcc; } template <class... TArgs> @@ -266,34 +270,51 @@ void VerifyQueries(const std::vector<std::vector<memgraph::communication::bolt:: ASSERT_EQ(got, expected); } +template <typename StorageType> +class DumpTest : public ::testing::Test { + public: + const std::string testSuite = "query_dump"; + std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_dump_class"}; + memgraph::query::InterpreterContext context{ + std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), + memgraph::query::InterpreterConfig{}, data_directory}; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(DumpTest, StorageTypes); + // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, EmptyGraph) { - memgraph::storage::Storage db; - ResultStreamFaker stream(&db); +TYPED_TEST(DumpTest, EmptyGraph) { + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } ASSERT_EQ(stream.GetResults().size(), 0); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, SingleVertex) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, SingleVertex) { { - auto dba = db.Access(); - CreateVertex(&dba, {}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", @@ -302,20 +323,19 @@ TEST(DumpTest, SingleVertex) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, VertexWithSingleLabel) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, VertexWithSingleLabel) { { - auto dba = db.Access(); - CreateVertex(&dba, {"Label1"}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {"Label1"}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1` {__mg_id__: 0});", @@ -324,20 +344,19 @@ TEST(DumpTest, VertexWithSingleLabel) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, VertexWithMultipleLabels) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, VertexWithMultipleLabels) { { - auto dba = db.Access(); - CreateVertex(&dba, {"Label1", "Label 2"}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {"Label1", "Label 2"}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, @@ -347,20 +366,19 @@ TEST(DumpTest, VertexWithMultipleLabels) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, VertexWithSingleProperty) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, VertexWithSingleProperty) { { - auto dba = db.Access(); - CreateVertex(&dba, {}, {{"prop", memgraph::storage::PropertyValue(42)}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {}, {{"prop", memgraph::storage::PropertyValue(42)}}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, `prop`: 42});", @@ -369,22 +387,21 @@ TEST(DumpTest, VertexWithSingleProperty) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, MultipleVertices) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, MultipleVertices) { { - auto dba = db.Access(); - CreateVertex(&dba, {}, {}, false); - CreateVertex(&dba, {}, {}, false); - CreateVertex(&dba, {}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {}, {}, false); + CreateVertex(dba.get(), {}, {}, false); + CreateVertex(dba.get(), {}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", @@ -393,10 +410,9 @@ TEST(DumpTest, MultipleVertices) { } } -TEST(DumpTest, PropertyValue) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, PropertyValue) { { - auto dba = db.Access(); + auto dba = this->context.db->Access(); auto null_value = memgraph::storage::PropertyValue(); auto int_value = memgraph::storage::PropertyValue(13); auto bool_value = memgraph::storage::PropertyValue(true); @@ -414,16 +430,16 @@ TEST(DumpTest, PropertyValue) { auto dur = memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::Duration, memgraph::utils::Duration({3, 4, 5, 6, 10, 11}).microseconds)); auto list_value = memgraph::storage::PropertyValue({map_value, null_value, double_value, dt, lt, ldt, dur}); - CreateVertex(&dba, {}, {{"p1", list_value}, {"p2", str_value}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + CreateVertex(dba.get(), {}, {{"p1", list_value}, {"p2", str_value}}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, @@ -436,22 +452,21 @@ TEST(DumpTest, PropertyValue) { } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, SingleEdge) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, SingleEdge) { { - auto dba = db.Access(); - auto u = CreateVertex(&dba, {}, {}, false); - auto v = CreateVertex(&dba, {}, {}, false); - CreateEdge(&dba, &u, &v, "EdgeType", {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + auto u = CreateVertex(dba.get(), {}, {}, false); + auto v = CreateVertex(dba.get(), {}, {}, false); + CreateEdge(dba.get(), &u, &v, "EdgeType", {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", @@ -463,25 +478,24 @@ TEST(DumpTest, SingleEdge) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, MultipleEdges) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, MultipleEdges) { { - auto dba = db.Access(); - auto u = CreateVertex(&dba, {}, {}, false); - auto v = CreateVertex(&dba, {}, {}, false); - auto w = CreateVertex(&dba, {}, {}, false); - CreateEdge(&dba, &u, &v, "EdgeType", {}, false); - CreateEdge(&dba, &v, &u, "EdgeType 2", {}, false); - CreateEdge(&dba, &v, &w, "EdgeType `!\"", {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + auto u = CreateVertex(dba.get(), {}, {}, false); + auto v = CreateVertex(dba.get(), {}, {}, false); + auto w = CreateVertex(dba.get(), {}, {}, false); + CreateEdge(dba.get(), &u, &v, "EdgeType", {}, false); + CreateEdge(dba.get(), &v, &u, "EdgeType 2", {}, false); + CreateEdge(dba.get(), &v, &w, "EdgeType `!\"", {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", @@ -497,22 +511,21 @@ TEST(DumpTest, MultipleEdges) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, EdgeWithProperties) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, EdgeWithProperties) { { - auto dba = db.Access(); - auto u = CreateVertex(&dba, {}, {}, false); - auto v = CreateVertex(&dba, {}, {}, false); - CreateEdge(&dba, &u, &v, "EdgeType", {{"prop", memgraph::storage::PropertyValue(13)}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + auto u = CreateVertex(dba.get(), {}, {}, false); + auto v = CreateVertex(dba.get(), {}, {}, false); + CreateEdge(dba.get(), &u, &v, "EdgeType", {{"prop", memgraph::storage::PropertyValue(13)}}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", @@ -524,22 +537,25 @@ TEST(DumpTest, EdgeWithProperties) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, IndicesKeys) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, IndicesKeys) { { - auto dba = db.Access(); - CreateVertex(&dba, {"Label1", "Label 2"}, {{"p", memgraph::storage::PropertyValue(1)}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {"Label1", "Label 2"}, {{"p", memgraph::storage::PropertyValue(1)}}, false); + ASSERT_FALSE(dba->Commit().HasError()); } - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("Label1"), db.NameToProperty("prop")).HasError()); - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("Label 2"), db.NameToProperty("prop `")).HasError()); + ASSERT_FALSE( + this->context.db->CreateIndex(this->context.db->NameToLabel("Label1"), this->context.db->NameToProperty("prop")) + .HasError()); + ASSERT_FALSE(this->context.db + ->CreateIndex(this->context.db->NameToLabel("Label 2"), this->context.db->NameToProperty("prop `")) + .HasError()); { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), "CREATE INDEX ON :`Label1`(`prop`);", "CREATE INDEX ON :`Label 2`(`prop ```);", @@ -549,24 +565,24 @@ TEST(DumpTest, IndicesKeys) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, ExistenceConstraints) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, ExistenceConstraints) { { - auto dba = db.Access(); - CreateVertex(&dba, {"L`abel 1"}, {{"prop", memgraph::storage::PropertyValue(1)}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {"L`abel 1"}, {{"prop", memgraph::storage::PropertyValue(1)}}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - auto res = db.CreateExistenceConstraint(db.NameToLabel("L`abel 1"), db.NameToProperty("prop")); + auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("L`abel 1"), + this->context.db->NameToProperty("prop"), {}); ASSERT_FALSE(res.HasError()); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:`L``abel 1`) ASSERT EXISTS (u.`prop`);", @@ -575,31 +591,31 @@ TEST(DumpTest, ExistenceConstraints) { } } -TEST(DumpTest, UniqueConstraints) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, UniqueConstraints) { { - auto dba = db.Access(); - CreateVertex(&dba, {"Label"}, + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {"Label"}, {{"prop", memgraph::storage::PropertyValue(1)}, {"prop2", memgraph::storage::PropertyValue(2)}}, false); - CreateVertex(&dba, {"Label"}, + CreateVertex(dba.get(), {"Label"}, {{"prop", memgraph::storage::PropertyValue(2)}, {"prop2", memgraph::storage::PropertyValue(2)}}, false); - ASSERT_FALSE(dba.Commit().HasError()); + ASSERT_FALSE(dba->Commit().HasError()); } { - auto res = - db.CreateUniqueConstraint(db.NameToLabel("Label"), {db.NameToProperty("prop"), db.NameToProperty("prop2")}); + auto res = this->context.db->CreateUniqueConstraint( + this->context.db->NameToLabel("Label"), + {this->context.db->NameToProperty("prop"), this->context.db->NameToProperty("prop2")}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } VerifyQueries(stream.GetResults(), @@ -615,25 +631,29 @@ TEST(DumpTest, UniqueConstraints) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, CheckStateVertexWithMultipleProperties) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) { { - auto dba = db.Access(); + auto dba = this->context.db->Access(); std::map<std::string, memgraph::storage::PropertyValue> prop1 = { {"nested1", memgraph::storage::PropertyValue(1337)}, {"nested2", memgraph::storage::PropertyValue(3.14)}}; + CreateVertex( - &dba, {"Label1", "Label2"}, + dba.get(), {"Label1", "Label2"}, {{"prop1", memgraph::storage::PropertyValue(prop1)}, {"prop2", memgraph::storage::PropertyValue("$'\t'")}}); - ASSERT_FALSE(dba.Commit().HasError()); + + ASSERT_FALSE(dba->Commit().HasError()); } - memgraph::storage::Storage db_dump; + auto data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_query_dump"; + memgraph::query::InterpreterContext interpreter_context(std::make_unique<TypeParam>(), + memgraph::query::InterpreterConfig{}, data_directory); + { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } const auto &results = stream.GetResults(); @@ -641,104 +661,111 @@ TEST(DumpTest, CheckStateVertexWithMultipleProperties) { for (const auto &item : results) { ASSERT_EQ(item.size(), 1); ASSERT_TRUE(item[0].IsString()); - Execute(&db_dump, item[0].ValueString()); + Execute(&interpreter_context, item[0].ValueString()); } } - ASSERT_EQ(GetState(&db), GetState(&db_dump)); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, CheckStateSimpleGraph) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, CheckStateSimpleGraph) { { - auto dba = db.Access(); - auto u = CreateVertex(&dba, {"Person"}, {{"name", memgraph::storage::PropertyValue("Ivan")}}); - auto v = CreateVertex(&dba, {"Person"}, {{"name", memgraph::storage::PropertyValue("Josko")}}); + auto dba = this->context.db->Access(); + auto u = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Ivan")}}); + auto v = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Josko")}}); auto w = CreateVertex( - &dba, {"Person"}, + dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Bosko")}, {"id", memgraph::storage::PropertyValue(0)}}); auto z = - CreateVertex(&dba, {"Person"}, + CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Buha")}, {"id", memgraph::storage::PropertyValue(1)}}); - CreateEdge(&dba, &u, &v, "Knows", {}); - CreateEdge(&dba, &v, &w, "Knows", {{"how_long", memgraph::storage::PropertyValue(5)}}); - CreateEdge(&dba, &w, &u, "Knows", {{"how", memgraph::storage::PropertyValue("distant past")}}); - CreateEdge(&dba, &v, &u, "Knows", {}); - CreateEdge(&dba, &v, &u, "Likes", {}); - CreateEdge(&dba, &z, &u, "Knows", {}); - CreateEdge(&dba, &w, &z, "Knows", {{"how", memgraph::storage::PropertyValue("school")}}); - CreateEdge(&dba, &w, &z, "Likes", {{"how", memgraph::storage::PropertyValue("very much")}}); - CreateEdge(&dba, &w, &z, "Date", + CreateEdge(dba.get(), &u, &v, "Knows", {}); + CreateEdge(dba.get(), &v, &w, "Knows", {{"how_long", memgraph::storage::PropertyValue(5)}}); + CreateEdge(dba.get(), &w, &u, "Knows", {{"how", memgraph::storage::PropertyValue("distant past")}}); + CreateEdge(dba.get(), &v, &u, "Knows", {}); + CreateEdge(dba.get(), &v, &u, "Likes", {}); + CreateEdge(dba.get(), &z, &u, "Knows", {}); + CreateEdge(dba.get(), &w, &z, "Knows", {{"how", memgraph::storage::PropertyValue("school")}}); + CreateEdge(dba.get(), &w, &z, "Likes", {{"how", memgraph::storage::PropertyValue("1234567890")}}); + CreateEdge(dba.get(), &w, &z, "Date", {{"time", memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::Date, memgraph::utils::Date({1994, 12, 7}).MicrosecondsSinceEpoch()))}}); - CreateEdge(&dba, &w, &z, "LocalTime", + CreateEdge(dba.get(), &w, &z, "LocalTime", {{"time", memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::LocalTime, memgraph::utils::LocalTime({14, 10, 44, 99, 99}).MicrosecondsSinceEpoch()))}}); CreateEdge( - &dba, &w, &z, "LocalDateTime", + dba.get(), &w, &z, "LocalDateTime", {{"time", memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::LocalDateTime, memgraph::utils::LocalDateTime({1994, 12, 7}, {14, 10, 44, 99, 99}).MicrosecondsSinceEpoch()))}}); - CreateEdge(&dba, &w, &z, "Duration", + CreateEdge(dba.get(), &w, &z, "Duration", {{"time", memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::Duration, memgraph::utils::Duration({3, 4, 5, 6, 10, 11}).microseconds))}}); - CreateEdge(&dba, &w, &z, "NegativeDuration", + CreateEdge(dba.get(), &w, &z, "NegativeDuration", {{"time", memgraph::storage::PropertyValue(memgraph::storage::TemporalData( memgraph::storage::TemporalType::Duration, memgraph::utils::Duration({-3, -4, -5, -6, -10, -11}).microseconds))}}); - ASSERT_FALSE(dba.Commit().HasError()); + ASSERT_FALSE(dba->Commit().HasError()); } { - auto ret = db.CreateExistenceConstraint(db.NameToLabel("Person"), db.NameToProperty("name")); + auto ret = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("Person"), + this->context.db->NameToProperty("name"), {}); ASSERT_FALSE(ret.HasError()); } { - auto ret = db.CreateUniqueConstraint(db.NameToLabel("Person"), {db.NameToProperty("name")}); + auto ret = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("Person"), + {this->context.db->NameToProperty("name")}, {}); ASSERT_TRUE(ret.HasValue()); ASSERT_EQ(ret.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("Person"), db.NameToProperty("id")).HasError()); - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("Person"), db.NameToProperty("unexisting_property")).HasError()); + ASSERT_FALSE( + this->context.db->CreateIndex(this->context.db->NameToLabel("Person"), this->context.db->NameToProperty("id")) + .HasError()); + ASSERT_FALSE(this->context.db + ->CreateIndex(this->context.db->NameToLabel("Person"), + this->context.db->NameToProperty("unexisting_property")) + .HasError()); - const auto &db_initial_state = GetState(&db); - memgraph::storage::Storage db_dump; + const auto &db_initial_state = GetState(this->context.db.get()); + auto data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_query_dump"; + memgraph::query::InterpreterContext interpreter_context(std::make_unique<TypeParam>(), + memgraph::query::InterpreterConfig{}, data_directory); { - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); } const auto &results = stream.GetResults(); // Indices and constraints are 4 queries and there must be at least one more // query for the data. ASSERT_GE(results.size(), 5); + int i = 0; for (const auto &item : results) { ASSERT_EQ(item.size(), 1); ASSERT_TRUE(item[0].IsString()); - Execute(&db_dump, item[0].ValueString()); + spdlog::debug("Query: {}", item[0].ValueString()); + Execute(&interpreter_context, item[0].ValueString()); + ++i; } } - ASSERT_EQ(GetState(&db), GetState(&db_dump)); - // Make sure that dump function doesn't make changes on the database. - ASSERT_EQ(GetState(&db), db_initial_state); + ASSERT_EQ(GetState(this->context.db.get()), db_initial_state); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, ExecuteDumpDatabase) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, ExecuteDumpDatabase) { { - auto dba = db.Access(); - CreateVertex(&dba, {}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } { - auto stream = Execute(&db, "DUMP DATABASE"); + auto stream = Execute(&this->context, "DUMP DATABASE"); const auto &header = stream.GetHeader(); const auto &results = stream.GetResults(); ASSERT_EQ(header.size(), 1U); @@ -757,11 +784,11 @@ TEST(DumpTest, ExecuteDumpDatabase) { class StatefulInterpreter { public: - explicit StatefulInterpreter(memgraph::storage::Storage *db) - : db_(db), context_(db_, memgraph::query::InterpreterConfig{}, data_directory_), interpreter_(&context_) {} + explicit StatefulInterpreter(memgraph::query::InterpreterContext *context) + : context_(context), interpreter_(context_) {} auto Execute(const std::string &query) { - ResultStreamFaker stream(db_); + ResultStreamFaker stream(context_->db.get()); auto [header, _, qid] = interpreter_.Prepare(query, {}, nullptr); stream.Header(header); @@ -774,8 +801,7 @@ class StatefulInterpreter { private: static const std::filesystem::path data_directory_; - memgraph::storage::Storage *db_; - memgraph::query::InterpreterContext context_; + memgraph::query::InterpreterContext *context_; memgraph::query::Interpreter interpreter_; }; @@ -783,9 +809,8 @@ const std::filesystem::path StatefulInterpreter::data_directory_{std::filesystem "MG_tests_unit_query_dump_stateful"}; // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) { - memgraph::storage::Storage db; - StatefulInterpreter interpreter(&db); +TYPED_TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) { + StatefulInterpreter interpreter(&this->context); // Begin the transaction before the vertex is created. interpreter.Execute("BEGIN"); @@ -802,9 +827,9 @@ TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) { // Create the vertex. { - auto dba = db.Access(); - CreateVertex(&dba, {}, {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->context.db->Access(); + CreateVertex(dba.get(), {}, {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } // Verify that nothing is dumped. @@ -846,67 +871,74 @@ TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(DumpTest, MultiplePartialPulls) { - memgraph::storage::Storage db; +TYPED_TEST(DumpTest, MultiplePartialPulls) { { // Create indices - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("PERSON"), db.NameToProperty("name")).HasError()); - ASSERT_FALSE(db.CreateIndex(db.NameToLabel("PERSON"), db.NameToProperty("surname")).HasError()); + ASSERT_FALSE( + this->context.db->CreateIndex(this->context.db->NameToLabel("PERSON"), this->context.db->NameToProperty("name")) + .HasError()); + ASSERT_FALSE(this->context.db + ->CreateIndex(this->context.db->NameToLabel("PERSON"), this->context.db->NameToProperty("surname")) + .HasError()); // Create existence constraints { - auto res = db.CreateExistenceConstraint(db.NameToLabel("PERSON"), db.NameToProperty("name")); + auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("PERSON"), + this->context.db->NameToProperty("name"), {}); ASSERT_FALSE(res.HasError()); } { - auto res = db.CreateExistenceConstraint(db.NameToLabel("PERSON"), db.NameToProperty("surname")); + auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("PERSON"), + this->context.db->NameToProperty("surname"), {}); ASSERT_FALSE(res.HasError()); } // Create unique constraints { - auto res = db.CreateUniqueConstraint(db.NameToLabel("PERSON"), {db.NameToProperty("name")}); + auto res = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("PERSON"), + {this->context.db->NameToProperty("name")}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } { - auto res = db.CreateUniqueConstraint(db.NameToLabel("PERSON"), {db.NameToProperty("surname")}); + auto res = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("PERSON"), + {this->context.db->NameToProperty("surname")}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); } - auto dba = db.Access(); - auto p1 = CreateVertex(&dba, {"PERSON"}, + auto dba = this->context.db->Access(); + auto p1 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person1")}, {"surname", memgraph::storage::PropertyValue("Unique1")}}, false); - auto p2 = CreateVertex(&dba, {"PERSON"}, + auto p2 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person2")}, {"surname", memgraph::storage::PropertyValue("Unique2")}}, false); - auto p3 = CreateVertex(&dba, {"PERSON"}, + auto p3 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person3")}, {"surname", memgraph::storage::PropertyValue("Unique3")}}, false); - auto p4 = CreateVertex(&dba, {"PERSON"}, + auto p4 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person4")}, {"surname", memgraph::storage::PropertyValue("Unique4")}}, false); - auto p5 = CreateVertex(&dba, {"PERSON"}, + auto p5 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person5")}, {"surname", memgraph::storage::PropertyValue("Unique5")}}, false); - CreateEdge(&dba, &p1, &p2, "REL", {}, false); - CreateEdge(&dba, &p1, &p3, "REL", {}, false); - CreateEdge(&dba, &p4, &p5, "REL", {}, false); - CreateEdge(&dba, &p2, &p5, "REL", {}, false); - ASSERT_FALSE(dba.Commit().HasError()); + CreateEdge(dba.get(), &p1, &p2, "REL", {}, false); + CreateEdge(dba.get(), &p1, &p3, "REL", {}, false); + CreateEdge(dba.get(), &p4, &p5, "REL", {}, false); + CreateEdge(dba.get(), &p2, &p5, "REL", {}, false); + ASSERT_FALSE(dba->Commit().HasError()); } - ResultStreamFaker stream(&db); + ResultStreamFaker stream(this->context.db.get()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); - auto acc = db.Access(); - memgraph::query::DbAccessor dba(&acc); + auto acc = this->context.db->Access(); + memgraph::query::DbAccessor dba(acc.get()); memgraph::query::PullPlanDump pullPlan{&dba}; diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index 6f51facdf..86ac5d624 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -19,7 +19,9 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "query/context.hpp" +#include "query/db_accessor.hpp" #include "query/frontend/ast/ast.hpp" #include "query/frontend/opencypher/parser.hpp" #include "query/interpret/awesome_memgraph_functions.hpp" @@ -27,6 +29,8 @@ #include "query/interpret/frame.hpp" #include "query/path.hpp" #include "query/typed_value.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "utils/exceptions.hpp" #include "utils/string.hpp" @@ -41,11 +45,15 @@ using testing::UnorderedElementsAre; namespace { +template <typename StorageType> class ExpressionEvaluatorTest : public ::testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + const std::string testSuite = "expression_evaluator"; + + memgraph::storage::Config config; + std::unique_ptr<memgraph::storage::Storage> db; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba; + memgraph::query::DbAccessor dba; AstStorage storage; memgraph::utils::MonotonicBufferResource mem{1024}; @@ -55,8 +63,20 @@ class ExpressionEvaluatorTest : public ::testing::Test { Frame frame{128}; ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, memgraph::storage::View::OLD}; + ExpressionEvaluatorTest() + : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), + db(new StorageType(config)), + storage_dba(db->Access()), + dba(storage_dba.get()) {} + + ~ExpressionEvaluatorTest() { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + Identifier *CreateIdentifierWithValue(std::string name, const TypedValue &value) { - auto id = storage.Create<Identifier>(name, true); + auto id = storage.template Create<Identifier>(name, true); auto symbol = symbol_table.CreateSymbol(name, true); id->MapTo(symbol); frame[symbol] = value; @@ -74,479 +94,539 @@ class ExpressionEvaluatorTest : public ::testing::Test { } }; -TEST_F(ExpressionEvaluatorTest, OrOperator) { - auto *op = - storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false)); - auto val1 = Eval(op); +// using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +using StorageTypes = ::testing::Types<memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ExpressionEvaluatorTest, StorageTypes); + +TYPED_TEST(ExpressionEvaluatorTest, OrOperator) { + auto *op = this->storage.template Create<OrOperator>(this->storage.template Create<PrimitiveLiteral>(true), + this->storage.template Create<PrimitiveLiteral>(false)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<OrOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true)); - auto val2 = Eval(op); + op = this->storage.template Create<OrOperator>(this->storage.template Create<PrimitiveLiteral>(true), + this->storage.template Create<PrimitiveLiteral>(true)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, XorOperator) { - auto *op = - storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(false)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, XorOperator) { + auto *op = this->storage.template Create<XorOperator>(this->storage.template Create<PrimitiveLiteral>(true), + this->storage.template Create<PrimitiveLiteral>(false)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<XorOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true)); - auto val2 = Eval(op); + op = this->storage.template Create<XorOperator>(this->storage.template Create<PrimitiveLiteral>(true), + this->storage.template Create<PrimitiveLiteral>(true)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), false); } -TEST_F(ExpressionEvaluatorTest, AndOperator) { - auto *op = - storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(true), storage.Create<PrimitiveLiteral>(true)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, AndOperator) { + auto *op = this->storage.template Create<AndOperator>(this->storage.template Create<PrimitiveLiteral>(true), + this->storage.template Create<PrimitiveLiteral>(true)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(true)); - auto val2 = Eval(op); + op = this->storage.template Create<AndOperator>(this->storage.template Create<PrimitiveLiteral>(false), + this->storage.template Create<PrimitiveLiteral>(true)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), false); } -TEST_F(ExpressionEvaluatorTest, AndOperatorShortCircuit) { +TYPED_TEST(ExpressionEvaluatorTest, AndOperatorShortCircuit) { { - auto *op = - storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(false), storage.Create<PrimitiveLiteral>(5)); - auto value = Eval(op); + auto *op = this->storage.template Create<AndOperator>(this->storage.template Create<PrimitiveLiteral>(false), + this->storage.template Create<PrimitiveLiteral>(5)); + auto value = this->Eval(op); EXPECT_EQ(value.ValueBool(), false); } { - auto *op = - storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(5), storage.Create<PrimitiveLiteral>(false)); + auto *op = this->storage.template Create<AndOperator>(this->storage.template Create<PrimitiveLiteral>(5), + this->storage.template Create<PrimitiveLiteral>(false)); // We are evaluating left to right, so we don't short circuit here and // raise due to `5`. This differs from neo4j, where they evaluate both // sides and return `false` without checking for type of the first // expression. - EXPECT_THROW(Eval(op), QueryRuntimeException); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } } -TEST_F(ExpressionEvaluatorTest, AndOperatorNull) { +TYPED_TEST(ExpressionEvaluatorTest, AndOperatorNull) { { // Null doesn't short circuit - auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(5)); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<AndOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(5)); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } { - auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(true)); - auto value = Eval(op); + auto *op = this->storage.template Create<AndOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(true)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { - auto *op = storage.Create<AndOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(false)); - auto value = Eval(op); + auto *op = this->storage.template Create<AndOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(false)); + auto value = this->Eval(op); ASSERT_TRUE(value.IsBool()); EXPECT_EQ(value.ValueBool(), false); } } -TEST_F(ExpressionEvaluatorTest, AdditionOperator) { - auto *op = storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, AdditionOperator) { + auto *op = this->storage.template Create<AdditionOperator>(this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(3)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 5); } -TEST_F(ExpressionEvaluatorTest, SubtractionOperator) { - auto *op = - storage.Create<SubtractionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, SubtractionOperator) { + auto *op = this->storage.template Create<SubtractionOperator>(this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(3)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), -1); } -TEST_F(ExpressionEvaluatorTest, MultiplicationOperator) { - auto *op = - storage.Create<MultiplicationOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, MultiplicationOperator) { + auto *op = this->storage.template Create<MultiplicationOperator>(this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(3)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 6); } -TEST_F(ExpressionEvaluatorTest, DivisionOperator) { - auto *op = - storage.Create<DivisionOperator>(storage.Create<PrimitiveLiteral>(50), storage.Create<PrimitiveLiteral>(10)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, DivisionOperator) { + auto *op = this->storage.template Create<DivisionOperator>(this->storage.template Create<PrimitiveLiteral>(50), + this->storage.template Create<PrimitiveLiteral>(10)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 5); } -TEST_F(ExpressionEvaluatorTest, ModOperator) { - auto *op = storage.Create<ModOperator>(storage.Create<PrimitiveLiteral>(65), storage.Create<PrimitiveLiteral>(10)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, ModOperator) { + auto *op = this->storage.template Create<ModOperator>(this->storage.template Create<PrimitiveLiteral>(65), + this->storage.template Create<PrimitiveLiteral>(10)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 5); } -TEST_F(ExpressionEvaluatorTest, EqualOperator) { - auto *op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, EqualOperator) { + auto *op = this->storage.template Create<EqualOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), false); - op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<EqualOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), true); - op = storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<EqualOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), false); } -TEST_F(ExpressionEvaluatorTest, NotEqualOperator) { - auto *op = - storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, NotEqualOperator) { + auto *op = this->storage.template Create<NotEqualOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<NotEqualOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), false); - op = storage.Create<NotEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<NotEqualOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, LessOperator) { - auto *op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, LessOperator) { + auto *op = this->storage.template Create<LessOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<LessOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), false); - op = storage.Create<LessOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<LessOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), false); } -TEST_F(ExpressionEvaluatorTest, GreaterOperator) { - auto *op = - storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, GreaterOperator) { + auto *op = this->storage.template Create<GreaterOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), false); - op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<GreaterOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), false); - op = storage.Create<GreaterOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<GreaterOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, LessEqualOperator) { - auto *op = - storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, LessEqualOperator) { + auto *op = this->storage.template Create<LessEqualOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), true); - op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<LessEqualOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), true); - op = storage.Create<LessEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<LessEqualOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), false); } -TEST_F(ExpressionEvaluatorTest, GreaterEqualOperator) { - auto *op = - storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(10), storage.Create<PrimitiveLiteral>(15)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, GreaterEqualOperator) { + auto *op = this->storage.template Create<GreaterEqualOperator>(this->storage.template Create<PrimitiveLiteral>(10), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), false); - op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(15), storage.Create<PrimitiveLiteral>(15)); - auto val2 = Eval(op); + op = this->storage.template Create<GreaterEqualOperator>(this->storage.template Create<PrimitiveLiteral>(15), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), true); - op = storage.Create<GreaterEqualOperator>(storage.Create<PrimitiveLiteral>(20), storage.Create<PrimitiveLiteral>(15)); - auto val3 = Eval(op); + op = this->storage.template Create<GreaterEqualOperator>(this->storage.template Create<PrimitiveLiteral>(20), + this->storage.template Create<PrimitiveLiteral>(15)); + auto val3 = this->Eval(op); ASSERT_EQ(val3.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, InListOperator) { - auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{ - storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>("a")}); +TYPED_TEST(ExpressionEvaluatorTest, InListOperator) { + auto *list_literal = this->storage.template Create<ListLiteral>(std::vector<Expression *>{ + this->storage.template Create<PrimitiveLiteral>(1), this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>("a")}); { // Element exists in list. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(2), list_literal); - auto value = Eval(op); + auto *op = + this->storage.template Create<InListOperator>(this->storage.template Create<PrimitiveLiteral>(2), list_literal); + auto value = this->Eval(op); EXPECT_EQ(value.ValueBool(), true); } { // Element doesn't exist in list. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal); - auto value = Eval(op); + auto *op = this->storage.template Create<InListOperator>(this->storage.template Create<PrimitiveLiteral>("x"), + list_literal); + auto value = this->Eval(op); EXPECT_EQ(value.ValueBool(), false); } { - auto *list_literal = storage.Create<ListLiteral>( - std::vector<Expression *>{storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>("a")}); + auto *list_literal = this->storage.template Create<ListLiteral>(std::vector<Expression *>{ + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(2), this->storage.template Create<PrimitiveLiteral>("a")}); // Element doesn't exist in list with null element. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), list_literal); - auto value = Eval(op); + auto *op = this->storage.template Create<InListOperator>(this->storage.template Create<PrimitiveLiteral>("x"), + list_literal); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Null list. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>("x"), - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value = Eval(op); + auto *op = this->storage.template Create<InListOperator>( + this->storage.template Create<PrimitiveLiteral>("x"), + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Null literal. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - list_literal); - auto value = Eval(op); + auto *op = this->storage.template Create<InListOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), list_literal); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Null literal, empty list. - auto *op = storage.Create<InListOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<ListLiteral>(std::vector<Expression *>())); - auto value = Eval(op); + auto *op = this->storage.template Create<InListOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<ListLiteral>(std::vector<Expression *>())); + auto value = this->Eval(op); EXPECT_FALSE(value.ValueBool()); } } -TEST_F(ExpressionEvaluatorTest, ListIndexing) { - auto *list_literal = storage.Create<ListLiteral>( - std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2), - storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)}); +TYPED_TEST(ExpressionEvaluatorTest, ListIndexing) { + auto *list_literal = this->storage.template Create<ListLiteral>(std::vector<Expression *>{ + this->storage.template Create<PrimitiveLiteral>(1), this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(3), this->storage.template Create<PrimitiveLiteral>(4)}); { // Legal indexing. - auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(2)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(2)); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 3); } { // Out of bounds indexing. - auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(4)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(4)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Out of bounds indexing with negative bound. - auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(-100)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Legal indexing with negative index. - auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(-2)); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 3); } { // Indexing with one operator being null. - auto *op = storage.Create<SubscriptOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(-2)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(-2)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Indexing with incompatible type. - auto *op = storage.Create<SubscriptOperator>(list_literal, storage.Create<PrimitiveLiteral>("bla")); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<SubscriptOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>("bla")); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } } -TEST_F(ExpressionEvaluatorTest, MapIndexing) { - auto *map_literal = storage.Create<MapLiteral>( - std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("a"), storage.Create<PrimitiveLiteral>(1)}, - {storage.GetPropertyIx("b"), storage.Create<PrimitiveLiteral>(2)}, - {storage.GetPropertyIx("c"), storage.Create<PrimitiveLiteral>(3)}}); +TYPED_TEST(ExpressionEvaluatorTest, MapIndexing) { + auto *map_literal = this->storage.template Create<MapLiteral>(std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("a"), this->storage.template Create<PrimitiveLiteral>(1)}, + {this->storage.GetPropertyIx("b"), this->storage.template Create<PrimitiveLiteral>(2)}, + {this->storage.GetPropertyIx("c"), this->storage.template Create<PrimitiveLiteral>(3)}}); { // Legal indexing. - auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("b")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_literal, + this->storage.template Create<PrimitiveLiteral>("b")); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 2); } { // Legal indexing, non-existing key. - auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>("z")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_literal, + this->storage.template Create<PrimitiveLiteral>("z")); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Wrong key type. - auto *op = storage.Create<SubscriptOperator>(map_literal, storage.Create<PrimitiveLiteral>(42)); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<SubscriptOperator>(map_literal, + this->storage.template Create<PrimitiveLiteral>(42)); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } { // Indexing with Null. - auto *op = storage.Create<SubscriptOperator>(map_literal, - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>( + map_literal, this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } } -TEST_F(ExpressionEvaluatorTest, MapProjectionIndexing) { - auto *map_variable = storage.Create<MapLiteral>( - std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}}); - auto *map_projection_literal = storage.Create<MapProjectionLiteral>( - map_variable, - std::unordered_map<PropertyIx, Expression *>{ - {storage.GetPropertyIx("a"), storage.Create<PrimitiveLiteral>(1)}, - {storage.GetPropertyIx("y"), storage.Create<PropertyLookup>(map_variable, storage.GetPropertyIx("y"))}}); +TYPED_TEST(ExpressionEvaluatorTest, MapProjectionIndexing) { + auto *map_variable = this->storage.template Create<MapLiteral>(std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("x"), this->storage.template Create<PrimitiveLiteral>(0)}}); + auto *map_projection_literal = this->storage.template Create<MapProjectionLiteral>( + map_variable, std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("a"), this->storage.template Create<PrimitiveLiteral>(1)}, + {this->storage.GetPropertyIx("y"), this->storage.template Create<PropertyLookup>( + map_variable, this->storage.GetPropertyIx("y"))}}); { // Legal indexing. - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("a")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>("a")); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 1); } { // Legal indexing; property created by PropertyLookup of a non-existent map variable key - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("y")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>("y")); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Legal indexing, non-existing property. - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("z")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>("z")); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Wrong key type. - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>(42)); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>(42)); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } { // Indexing with Null. - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>( + map_projection_literal, this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } } -TEST_F(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupBefore) { +TYPED_TEST(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupBefore) { // AllPropertiesLookup (.*) may contain properties whose names also occur in MapProjectionLiteral // The ones in MapProjectionLiteral are explicitly given and thus take precedence over those in AllPropertiesLookup // Test case: AllPropertiesLookup comes before the identically-named properties - auto *map_variable = storage.Create<MapLiteral>( - std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}}); - auto *map_projection_literal = storage.Create<MapProjectionLiteral>( - map_variable, std::unordered_map<PropertyIx, Expression *>{ - {storage.GetPropertyIx("*"), storage.Create<AllPropertiesLookup>(map_variable)}, - {storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(1)}}); + auto *map_variable = this->storage.template Create<MapLiteral>(std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("x"), this->storage.template Create<PrimitiveLiteral>(0)}}); + auto *map_projection_literal = this->storage.template Create<MapProjectionLiteral>( + map_variable, + std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("*"), this->storage.template Create<AllPropertiesLookup>(map_variable)}, + {this->storage.GetPropertyIx("x"), this->storage.template Create<PrimitiveLiteral>(1)}}); - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("x")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>("x")); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 1); } -TEST_F(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupAfter) { +TYPED_TEST(ExpressionEvaluatorTest, MapProjectionAllPropertiesLookupAfter) { // AllPropertiesLookup (.*) may contain properties whose names also occur in MapProjectionLiteral // The ones in MapProjectionLiteral are explicitly given and thus take precedence over those in AllPropertiesLookup // Test case: AllPropertiesLookup comes after the identically-named properties - auto *map_variable = storage.Create<MapLiteral>( - std::unordered_map<PropertyIx, Expression *>{{storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(0)}}); - auto *map_projection_literal = storage.Create<MapProjectionLiteral>( - map_variable, std::unordered_map<PropertyIx, Expression *>{ - {storage.GetPropertyIx("x"), storage.Create<PrimitiveLiteral>(1)}, - {storage.GetPropertyIx("*"), storage.Create<AllPropertiesLookup>(map_variable)}}); + auto *map_variable = this->storage.template Create<MapLiteral>(std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("x"), this->storage.template Create<PrimitiveLiteral>(0)}}); + auto *map_projection_literal = this->storage.template Create<MapProjectionLiteral>( + map_variable, + std::unordered_map<PropertyIx, Expression *>{ + {this->storage.GetPropertyIx("x"), this->storage.template Create<PrimitiveLiteral>(1)}, + {this->storage.GetPropertyIx("*"), this->storage.template Create<AllPropertiesLookup>(map_variable)}}); - auto *op = storage.Create<SubscriptOperator>(map_projection_literal, storage.Create<PrimitiveLiteral>("x")); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(map_projection_literal, + this->storage.template Create<PrimitiveLiteral>("x")); + auto value = this->Eval(op); EXPECT_EQ(value.ValueInt(), 1); } -TEST_F(ExpressionEvaluatorTest, VertexAndEdgeIndexing) { - auto edge_type = dba.NameToEdgeType("edge_type"); - auto prop = dba.NameToProperty("prop"); - auto v1 = dba.InsertVertex(); - auto e11 = dba.InsertEdge(&v1, &v1, edge_type); +TYPED_TEST(ExpressionEvaluatorTest, VertexAndEdgeIndexing) { + auto edge_type = this->dba.NameToEdgeType("edge_type"); + auto prop = this->dba.NameToProperty("prop"); + auto v1 = this->dba.InsertVertex(); + auto e11 = this->dba.InsertEdge(&v1, &v1, edge_type); ASSERT_TRUE(e11.HasValue()); ASSERT_TRUE(v1.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue()); ASSERT_TRUE(e11->SetProperty(prop, memgraph::storage::PropertyValue(43)).HasValue()); - dba.AdvanceCommand(); + this->dba.AdvanceCommand(); - auto *vertex_id = CreateIdentifierWithValue("v1", TypedValue(v1)); - auto *edge_id = CreateIdentifierWithValue("e11", TypedValue(*e11)); + auto *vertex_id = this->CreateIdentifierWithValue("v1", TypedValue(v1)); + auto *edge_id = this->CreateIdentifierWithValue("e11", TypedValue(*e11)); { // Legal indexing. - auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("prop")); - auto value1 = Eval(op1); + auto *op1 = this->storage.template Create<SubscriptOperator>( + vertex_id, this->storage.template Create<PrimitiveLiteral>("prop")); + auto value1 = this->Eval(op1); EXPECT_EQ(value1.ValueInt(), 42); - auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("prop")); - auto value2 = Eval(op2); + auto *op2 = this->storage.template Create<SubscriptOperator>( + edge_id, this->storage.template Create<PrimitiveLiteral>("prop")); + auto value2 = this->Eval(op2); EXPECT_EQ(value2.ValueInt(), 43); } { // Legal indexing, non-existing key. - auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>("blah")); - auto value1 = Eval(op1); + auto *op1 = this->storage.template Create<SubscriptOperator>( + vertex_id, this->storage.template Create<PrimitiveLiteral>("blah")); + auto value1 = this->Eval(op1); EXPECT_TRUE(value1.IsNull()); - auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>("blah")); - auto value2 = Eval(op2); + auto *op2 = this->storage.template Create<SubscriptOperator>( + edge_id, this->storage.template Create<PrimitiveLiteral>("blah")); + auto value2 = this->Eval(op2); EXPECT_TRUE(value2.IsNull()); } { // Wrong key type. - auto *op1 = storage.Create<SubscriptOperator>(vertex_id, storage.Create<PrimitiveLiteral>(1)); - EXPECT_THROW(Eval(op1), QueryRuntimeException); + auto *op1 = + this->storage.template Create<SubscriptOperator>(vertex_id, this->storage.template Create<PrimitiveLiteral>(1)); + EXPECT_THROW(this->Eval(op1), QueryRuntimeException); - auto *op2 = storage.Create<SubscriptOperator>(edge_id, storage.Create<PrimitiveLiteral>(1)); - EXPECT_THROW(Eval(op2), QueryRuntimeException); + auto *op2 = + this->storage.template Create<SubscriptOperator>(edge_id, this->storage.template Create<PrimitiveLiteral>(1)); + EXPECT_THROW(this->Eval(op2), QueryRuntimeException); } { // Indexing with Null. - auto *op1 = storage.Create<SubscriptOperator>(vertex_id, - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value1 = Eval(op1); + auto *op1 = this->storage.template Create<SubscriptOperator>( + vertex_id, this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value1 = this->Eval(op1); EXPECT_TRUE(value1.IsNull()); - auto *op2 = storage.Create<SubscriptOperator>(edge_id, - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value2 = Eval(op2); + auto *op2 = this->storage.template Create<SubscriptOperator>( + edge_id, this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value2 = this->Eval(op2); EXPECT_TRUE(value2.IsNull()); } } -TEST_F(ExpressionEvaluatorTest, TypedValueListIndexing) { - auto list_vector = memgraph::utils::pmr::vector<TypedValue>(ctx.memory); +TYPED_TEST(ExpressionEvaluatorTest, TypedValueListIndexing) { + auto list_vector = memgraph::utils::pmr::vector<TypedValue>(this->ctx.memory); list_vector.emplace_back("string1"); list_vector.emplace_back(TypedValue("string2")); - auto *identifier = storage.Create<Identifier>("n"); - auto node_symbol = symbol_table.CreateSymbol("n", true); + auto *identifier = this->storage.template Create<Identifier>("n"); + auto node_symbol = this->symbol_table.CreateSymbol("n", true); identifier->MapTo(node_symbol); - frame[node_symbol] = TypedValue(list_vector, ctx.memory); + this->frame[node_symbol] = TypedValue(list_vector, this->ctx.memory); { // Legal indexing. - auto *op = storage.Create<SubscriptOperator>(identifier, storage.Create<PrimitiveLiteral>(0)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(identifier, + this->storage.template Create<PrimitiveLiteral>(0)); + auto value = this->Eval(op); EXPECT_EQ(value.ValueString(), "string1"); } { // Out of bounds indexing - auto *op = storage.Create<SubscriptOperator>(identifier, storage.Create<PrimitiveLiteral>(3)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(identifier, + this->storage.template Create<PrimitiveLiteral>(3)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Out of bounds indexing with negative bound. - auto *op = storage.Create<SubscriptOperator>(identifier, storage.Create<PrimitiveLiteral>(-100)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(identifier, + this->storage.template Create<PrimitiveLiteral>(-100)); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } { // Legal indexing with negative index. - auto *op = storage.Create<SubscriptOperator>(identifier, storage.Create<PrimitiveLiteral>(-2)); - auto value = Eval(op); + auto *op = this->storage.template Create<SubscriptOperator>(identifier, + this->storage.template Create<PrimitiveLiteral>(-2)); + auto value = this->Eval(op); EXPECT_EQ(value.ValueString(), "string1"); } { // Indexing with incompatible type. - auto *op = storage.Create<SubscriptOperator>(identifier, storage.Create<PrimitiveLiteral>("bla")); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<SubscriptOperator>(identifier, + this->storage.template Create<PrimitiveLiteral>("bla")); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } } -TEST_F(ExpressionEvaluatorTest, ListSlicingOperator) { - auto *list_literal = storage.Create<ListLiteral>( - std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), storage.Create<PrimitiveLiteral>(2), - storage.Create<PrimitiveLiteral>(3), storage.Create<PrimitiveLiteral>(4)}); +TYPED_TEST(ExpressionEvaluatorTest, ListSlicingOperator) { + auto *list_literal = this->storage.template Create<ListLiteral>(std::vector<Expression *>{ + this->storage.template Create<PrimitiveLiteral>(1), this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(3), this->storage.template Create<PrimitiveLiteral>(4)}); auto extract_ints = [](TypedValue list) { std::vector<int64_t> int_list; @@ -557,175 +637,185 @@ TEST_F(ExpressionEvaluatorTest, ListSlicingOperator) { }; { // Legal slicing with both bounds defined. - auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2), - storage.Create<PrimitiveLiteral>(4)); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(4)); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre(3, 4)); } { // Legal slicing with negative bound. - auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2), - storage.Create<PrimitiveLiteral>(-1)); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(-1)); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre(3)); } { // Lower bound larger than upper bound. - auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(2), - storage.Create<PrimitiveLiteral>(-4)); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(2), + this->storage.template Create<PrimitiveLiteral>(-4)); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre()); } { // Bounds ouf or range. - auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-100), - storage.Create<PrimitiveLiteral>(10)); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>(list_literal, + this->storage.template Create<PrimitiveLiteral>(-100), + this->storage.template Create<PrimitiveLiteral>(10)); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3, 4)); } { // Lower bound undefined. - auto *op = storage.Create<ListSlicingOperator>(list_literal, nullptr, storage.Create<PrimitiveLiteral>(3)); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>(list_literal, nullptr, + this->storage.template Create<PrimitiveLiteral>(3)); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre(1, 2, 3)); } { // Upper bound undefined. - auto *op = storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2), nullptr); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>( + list_literal, this->storage.template Create<PrimitiveLiteral>(-2), nullptr); + auto value = this->Eval(op); EXPECT_THAT(extract_ints(value), ElementsAre(3, 4)); } { // Bound of illegal type and null value bound. - auto *op = storage.Create<ListSlicingOperator>(list_literal, - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>("mirko")); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<ListSlicingOperator>( + list_literal, this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>("mirko")); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } { // List of illegal type. - auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>("a"), - storage.Create<PrimitiveLiteral>(-2), nullptr); - EXPECT_THROW(Eval(op), QueryRuntimeException); + auto *op = this->storage.template Create<ListSlicingOperator>(this->storage.template Create<PrimitiveLiteral>("a"), + this->storage.template Create<PrimitiveLiteral>(-2), + nullptr); + EXPECT_THROW(this->Eval(op), QueryRuntimeException); } { // Null value list with undefined upper bound. - auto *op = storage.Create<ListSlicingOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), - storage.Create<PrimitiveLiteral>(-2), nullptr); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue()), + this->storage.template Create<PrimitiveLiteral>(-2), nullptr); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); ; } { // Null value index. - auto *op = - storage.Create<ListSlicingOperator>(list_literal, storage.Create<PrimitiveLiteral>(-2), - storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto value = Eval(op); + auto *op = this->storage.template Create<ListSlicingOperator>( + list_literal, this->storage.template Create<PrimitiveLiteral>(-2), + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); ; } } -TEST_F(ExpressionEvaluatorTest, IfOperator) { - auto *then_expression = storage.Create<PrimitiveLiteral>(10); - auto *else_expression = storage.Create<PrimitiveLiteral>(20); +TYPED_TEST(ExpressionEvaluatorTest, IfOperator) { + auto *then_expression = this->storage.template Create<PrimitiveLiteral>(10); + auto *else_expression = this->storage.template Create<PrimitiveLiteral>(20); { - auto *condition_true = - storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(2)); - auto *op = storage.Create<IfOperator>(condition_true, then_expression, else_expression); - auto value = Eval(op); + auto *condition_true = this->storage.template Create<EqualOperator>( + this->storage.template Create<PrimitiveLiteral>(2), this->storage.template Create<PrimitiveLiteral>(2)); + auto *op = this->storage.template Create<IfOperator>(condition_true, then_expression, else_expression); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 10); } { - auto *condition_false = - storage.Create<EqualOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3)); - auto *op = storage.Create<IfOperator>(condition_false, then_expression, else_expression); - auto value = Eval(op); + auto *condition_false = this->storage.template Create<EqualOperator>( + this->storage.template Create<PrimitiveLiteral>(2), this->storage.template Create<PrimitiveLiteral>(3)); + auto *op = this->storage.template Create<IfOperator>(condition_false, then_expression, else_expression); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 20); } { - auto *condition_exception = - storage.Create<AdditionOperator>(storage.Create<PrimitiveLiteral>(2), storage.Create<PrimitiveLiteral>(3)); - auto *op = storage.Create<IfOperator>(condition_exception, then_expression, else_expression); - ASSERT_THROW(Eval(op), QueryRuntimeException); + auto *condition_exception = this->storage.template Create<AdditionOperator>( + this->storage.template Create<PrimitiveLiteral>(2), this->storage.template Create<PrimitiveLiteral>(3)); + auto *op = this->storage.template Create<IfOperator>(condition_exception, then_expression, else_expression); + ASSERT_THROW(this->Eval(op), QueryRuntimeException); } } -TEST_F(ExpressionEvaluatorTest, NotOperator) { - auto *op = storage.Create<NotOperator>(storage.Create<PrimitiveLiteral>(false)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, NotOperator) { + auto *op = this->storage.template Create<NotOperator>(this->storage.template Create<PrimitiveLiteral>(false)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, UnaryPlusOperator) { - auto *op = storage.Create<UnaryPlusOperator>(storage.Create<PrimitiveLiteral>(5)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, UnaryPlusOperator) { + auto *op = this->storage.template Create<UnaryPlusOperator>(this->storage.template Create<PrimitiveLiteral>(5)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), 5); } -TEST_F(ExpressionEvaluatorTest, UnaryMinusOperator) { - auto *op = storage.Create<UnaryMinusOperator>(storage.Create<PrimitiveLiteral>(5)); - auto value = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, UnaryMinusOperator) { + auto *op = this->storage.template Create<UnaryMinusOperator>(this->storage.template Create<PrimitiveLiteral>(5)); + auto value = this->Eval(op); ASSERT_EQ(value.ValueInt(), -5); } -TEST_F(ExpressionEvaluatorTest, IsNullOperator) { - auto *op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(1)); - auto val1 = Eval(op); +TYPED_TEST(ExpressionEvaluatorTest, IsNullOperator) { + auto *op = this->storage.template Create<IsNullOperator>(this->storage.template Create<PrimitiveLiteral>(1)); + auto val1 = this->Eval(op); ASSERT_EQ(val1.ValueBool(), false); - op = storage.Create<IsNullOperator>(storage.Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); - auto val2 = Eval(op); + op = this->storage.template Create<IsNullOperator>( + this->storage.template Create<PrimitiveLiteral>(memgraph::storage::PropertyValue())); + auto val2 = this->Eval(op); ASSERT_EQ(val2.ValueBool(), true); } -TEST_F(ExpressionEvaluatorTest, LabelsTest) { - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("ANIMAL")).HasValue()); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("DOG")).HasValue()); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("NICE_DOG")).HasValue()); - dba.AdvanceCommand(); - auto *identifier = storage.Create<Identifier>("n"); - auto node_symbol = symbol_table.CreateSymbol("n", true); +TYPED_TEST(ExpressionEvaluatorTest, LabelsTest) { + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("ANIMAL")).HasValue()); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("DOG")).HasValue()); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("NICE_DOG")).HasValue()); + this->dba.AdvanceCommand(); + auto *identifier = this->storage.template Create<Identifier>("n"); + auto node_symbol = this->symbol_table.CreateSymbol("n", true); identifier->MapTo(node_symbol); - frame[node_symbol] = TypedValue(v1); + this->frame[node_symbol] = TypedValue(v1); { - auto *op = storage.Create<LabelsTest>( - identifier, std::vector<LabelIx>{storage.GetLabelIx("DOG"), storage.GetLabelIx("ANIMAL")}); - auto value = Eval(op); + auto *op = this->storage.template Create<LabelsTest>( + identifier, std::vector<LabelIx>{this->storage.GetLabelIx("DOG"), this->storage.GetLabelIx("ANIMAL")}); + auto value = this->Eval(op); EXPECT_EQ(value.ValueBool(), true); } { - auto *op = storage.Create<LabelsTest>( - identifier, - std::vector<LabelIx>{storage.GetLabelIx("DOG"), storage.GetLabelIx("BAD_DOG"), storage.GetLabelIx("ANIMAL")}); - auto value = Eval(op); + auto *op = this->storage.template Create<LabelsTest>( + identifier, std::vector<LabelIx>{this->storage.GetLabelIx("DOG"), this->storage.GetLabelIx("BAD_DOG"), + this->storage.GetLabelIx("ANIMAL")}); + auto value = this->Eval(op); EXPECT_EQ(value.ValueBool(), false); } { - frame[node_symbol] = TypedValue(); - auto *op = storage.Create<LabelsTest>( - identifier, - std::vector<LabelIx>{storage.GetLabelIx("DOG"), storage.GetLabelIx("BAD_DOG"), storage.GetLabelIx("ANIMAL")}); - auto value = Eval(op); + this->frame[node_symbol] = TypedValue(); + auto *op = this->storage.template Create<LabelsTest>( + identifier, std::vector<LabelIx>{this->storage.GetLabelIx("DOG"), this->storage.GetLabelIx("BAD_DOG"), + this->storage.GetLabelIx("ANIMAL")}); + auto value = this->Eval(op); EXPECT_TRUE(value.IsNull()); } } -TEST_F(ExpressionEvaluatorTest, Aggregation) { - auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42), nullptr, Aggregation::Op::COUNT, false); - auto aggr_sym = symbol_table.CreateSymbol("aggr", true); +TYPED_TEST(ExpressionEvaluatorTest, Aggregation) { + auto aggr = this->storage.template Create<Aggregation>(this->storage.template Create<PrimitiveLiteral>(42), nullptr, + Aggregation::Op::COUNT, false); + auto aggr_sym = this->symbol_table.CreateSymbol("aggr", true); aggr->MapTo(aggr_sym); - frame[aggr_sym] = TypedValue(1); - auto value = Eval(aggr); + this->frame[aggr_sym] = TypedValue(1); + auto value = this->Eval(aggr); EXPECT_EQ(value.ValueInt(), 1); } -TEST_F(ExpressionEvaluatorTest, ListLiteral) { - auto *list_literal = storage.Create<ListLiteral>(std::vector<Expression *>{storage.Create<PrimitiveLiteral>(1), - storage.Create<PrimitiveLiteral>("bla"), - storage.Create<PrimitiveLiteral>(true)}); - TypedValue result = Eval(list_literal); +TYPED_TEST(ExpressionEvaluatorTest, ListLiteral) { + auto *list_literal = this->storage.template Create<ListLiteral>(std::vector<Expression *>{ + this->storage.template Create<PrimitiveLiteral>(1), this->storage.template Create<PrimitiveLiteral>("bla"), + this->storage.template Create<PrimitiveLiteral>(true)}); + TypedValue result = this->Eval(list_literal); ASSERT_TRUE(result.IsList()); auto &result_elems = result.ValueList(); ASSERT_EQ(3, result_elems.size()); @@ -737,289 +827,289 @@ TEST_F(ExpressionEvaluatorTest, ListLiteral) { ; } -TEST_F(ExpressionEvaluatorTest, ParameterLookup) { - ctx.parameters.Add(0, memgraph::storage::PropertyValue(42)); - auto *param_lookup = storage.Create<ParameterLookup>(0); - auto value = Eval(param_lookup); +TYPED_TEST(ExpressionEvaluatorTest, ParameterLookup) { + this->ctx.parameters.Add(0, memgraph::storage::PropertyValue(42)); + auto *param_lookup = this->storage.template Create<ParameterLookup>(0); + auto value = this->Eval(param_lookup); ASSERT_TRUE(value.IsInt()); EXPECT_EQ(value.ValueInt(), 42); } -TEST_F(ExpressionEvaluatorTest, FunctionAll1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAll1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *all = ALL("x", LIST(LITERAL(1), LITERAL(1)), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(all); + auto value = this->Eval(all); ASSERT_TRUE(value.IsBool()); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAll2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAll2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *all = ALL("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(all); + auto value = this->Eval(all); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAllNullList) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAllNullList) { AstStorage storage; auto *all = ALL("x", LITERAL(memgraph::storage::PropertyValue()), WHERE(LITERAL(true))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); - auto value = Eval(all); + auto value = this->Eval(all); EXPECT_TRUE(value.IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAllNullElementInList1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *all = ALL("x", LIST(LITERAL(1), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(all); + auto value = this->Eval(all); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAllNullElementInList2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAllNullElementInList2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *all = ALL("x", LIST(LITERAL(2), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(all); + auto value = this->Eval(all); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAllWhereWrongType) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAllWhereWrongType) { AstStorage storage; auto *all = ALL("x", LIST(LITERAL(1)), WHERE(LITERAL(2))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); all->identifier_->MapTo(x_sym); - EXPECT_THROW(Eval(all), QueryRuntimeException); + EXPECT_THROW(this->Eval(all), QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, FunctionSingle1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionSingle1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); single->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(single); + auto value = this->Eval(single); ASSERT_TRUE(value.IsBool()); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionSingle2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionSingle2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(GREATER(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); single->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(single); + auto value = this->Eval(single); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionSingleNullList) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionSingleNullList) { AstStorage storage; auto *single = SINGLE("x", LITERAL(memgraph::storage::PropertyValue()), WHERE(LITERAL(true))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); single->identifier_->MapTo(x_sym); - auto value = Eval(single); + auto value = this->Eval(single); EXPECT_TRUE(value.IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionSingleNullElementInList1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *single = SINGLE("x", LIST(LITERAL(1), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); single->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(single); + auto value = this->Eval(single); ASSERT_TRUE(value.IsBool()); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionSingleNullElementInList2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionSingleNullElementInList2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *single = SINGLE("x", LIST(LITERAL(2), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); single->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(single); + auto value = this->Eval(single); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAny1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAny1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); ASSERT_TRUE(value.IsBool()); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAny2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAny2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *any = ANY("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAnyNullList) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAnyNullList) { AstStorage storage; auto *any = ANY("x", LITERAL(memgraph::storage::PropertyValue()), WHERE(LITERAL(true))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); EXPECT_TRUE(value.IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAnyNullElementInList1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *any = ANY("x", LIST(LITERAL(0), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAnyNullElementInList2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAnyNullElementInList2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *any = ANY("x", LIST(LITERAL(1), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionAnyWhereWrongType) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionAnyWhereWrongType) { AstStorage storage; auto *any = ANY("x", LIST(LITERAL(1)), WHERE(LITERAL(2))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); - EXPECT_THROW(Eval(any), QueryRuntimeException); + EXPECT_THROW(this->Eval(any), QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, FunctionNone1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNone1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); none->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(none); + auto value = this->Eval(none); ASSERT_TRUE(value.IsBool()); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionNone2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNone2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *none = NONE("x", LIST(LITERAL(1), LITERAL(2)), WHERE(EQ(ident_x, LITERAL(1)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); none->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(none); + auto value = this->Eval(none); ASSERT_TRUE(value.IsBool()); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionNoneNullList) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNoneNullList) { AstStorage storage; auto *none = NONE("x", LITERAL(memgraph::storage::PropertyValue()), WHERE(LITERAL(true))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); none->identifier_->MapTo(x_sym); - auto value = Eval(none); + auto value = this->Eval(none); EXPECT_TRUE(value.IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList1) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNoneNullElementInList1) { AstStorage storage; auto *ident_x = IDENT("x"); auto *any = NONE("x", LIST(LITERAL(1), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); any->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(any); + auto value = this->Eval(any); EXPECT_TRUE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionNoneNullElementInList2) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNoneNullElementInList2) { AstStorage storage; auto *ident_x = IDENT("x"); auto *none = NONE("x", LIST(LITERAL(0), LITERAL(memgraph::storage::PropertyValue())), WHERE(EQ(ident_x, LITERAL(0)))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); none->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(none); + auto value = this->Eval(none); EXPECT_FALSE(value.ValueBool()); } -TEST_F(ExpressionEvaluatorTest, FunctionNoneWhereWrongType) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionNoneWhereWrongType) { AstStorage storage; auto *none = NONE("x", LIST(LITERAL(1)), WHERE(LITERAL(2))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); none->identifier_->MapTo(x_sym); - EXPECT_THROW(Eval(none), QueryRuntimeException); + EXPECT_THROW(this->Eval(none), QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, FunctionReduce) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionReduce) { AstStorage storage; auto *ident_sum = IDENT("sum"); auto *ident_x = IDENT("x"); auto *reduce = REDUCE("sum", LITERAL(0), "x", LIST(LITERAL(1), LITERAL(2)), ADD(ident_sum, ident_x)); - const auto sum_sym = symbol_table.CreateSymbol("sum", true); + const auto sum_sym = this->symbol_table.CreateSymbol("sum", true); reduce->accumulator_->MapTo(sum_sym); ident_sum->MapTo(sum_sym); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); reduce->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(reduce); + auto value = this->Eval(reduce); ASSERT_TRUE(value.IsInt()); EXPECT_EQ(value.ValueInt(), 3); } -TEST_F(ExpressionEvaluatorTest, FunctionExtract) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionExtract) { AstStorage storage; auto *ident_x = IDENT("x"); auto *extract = EXTRACT("x", LIST(LITERAL(1), LITERAL(2), LITERAL(memgraph::storage::PropertyValue())), ADD(ident_x, LITERAL(1))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); extract->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(extract); + auto value = this->Eval(extract); EXPECT_TRUE(value.IsList()); ; auto result = value.ValueList(); @@ -1028,344 +1118,359 @@ TEST_F(ExpressionEvaluatorTest, FunctionExtract) { EXPECT_TRUE(result[2].IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionExtractNull) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionExtractNull) { AstStorage storage; auto *ident_x = IDENT("x"); auto *extract = EXTRACT("x", LITERAL(memgraph::storage::PropertyValue()), ADD(ident_x, LITERAL(1))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); extract->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - auto value = Eval(extract); + auto value = this->Eval(extract); EXPECT_TRUE(value.IsNull()); } -TEST_F(ExpressionEvaluatorTest, FunctionExtractExceptions) { +TYPED_TEST(ExpressionEvaluatorTest, FunctionExtractExceptions) { AstStorage storage; auto *ident_x = IDENT("x"); auto *extract = EXTRACT("x", LITERAL("bla"), ADD(ident_x, LITERAL(1))); - const auto x_sym = symbol_table.CreateSymbol("x", true); + const auto x_sym = this->symbol_table.CreateSymbol("x", true); extract->identifier_->MapTo(x_sym); ident_x->MapTo(x_sym); - EXPECT_THROW(Eval(extract), QueryRuntimeException); + EXPECT_THROW(this->Eval(extract), QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, Coalesce) { +TYPED_TEST(ExpressionEvaluatorTest, Coalesce) { // coalesce() - EXPECT_THROW(Eval(COALESCE()), QueryRuntimeException); + EXPECT_THROW(this->Eval(COALESCE()), QueryRuntimeException); // coalesce(null, null) - EXPECT_TRUE(Eval(COALESCE(LITERAL(TypedValue()), LITERAL(TypedValue()))).IsNull()); + EXPECT_TRUE(this->Eval(COALESCE(LITERAL(TypedValue()), LITERAL(TypedValue()))).IsNull()); // coalesce(null, 2, 3) - EXPECT_EQ(Eval(COALESCE(LITERAL(TypedValue()), LITERAL(2), LITERAL(3))).ValueInt(), 2); + EXPECT_EQ(this->Eval(COALESCE(LITERAL(TypedValue()), LITERAL(2), LITERAL(3))).ValueInt(), 2); // coalesce(null, 2, assert(false), 3) - EXPECT_EQ(Eval(COALESCE(LITERAL(TypedValue()), LITERAL(2), FN("ASSERT", LITERAL(false)), LITERAL(3))).ValueInt(), 2); + EXPECT_EQ( + this->Eval(COALESCE(LITERAL(TypedValue()), LITERAL(2), FN("ASSERT", LITERAL(false)), LITERAL(3))).ValueInt(), 2); // (null, assert(false)) - EXPECT_THROW(Eval(COALESCE(LITERAL(TypedValue()), FN("ASSERT", LITERAL(false)))), QueryRuntimeException); + EXPECT_THROW(this->Eval(COALESCE(LITERAL(TypedValue()), FN("ASSERT", LITERAL(false)))), QueryRuntimeException); // coalesce([null, null]) - EXPECT_FALSE(Eval(COALESCE(LITERAL(TypedValue(std::vector<TypedValue>{TypedValue(), TypedValue()})))).IsNull()); + EXPECT_FALSE(this->Eval(COALESCE(LITERAL(TypedValue(std::vector<TypedValue>{TypedValue(), TypedValue()})))).IsNull()); } -TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidArguments) { - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(TypedValue()), LITERAL("regex"))).IsNull()); - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL(3), LITERAL("regex"))).IsNull()); - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LIST(LITERAL("string")), LITERAL("regex"))).IsNull()); - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(TypedValue()))).IsNull()); - EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LITERAL(42))), QueryRuntimeException); - EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("string"), LIST(LITERAL("regex")))), QueryRuntimeException); +TYPED_TEST(ExpressionEvaluatorTest, RegexMatchInvalidArguments) { + EXPECT_TRUE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL(TypedValue()), LITERAL("regex"))).IsNull()); + EXPECT_TRUE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL(3), LITERAL("regex"))).IsNull()); + EXPECT_TRUE( + this->Eval(this->storage.template Create<RegexMatch>(LIST(LITERAL("string")), LITERAL("regex"))).IsNull()); + EXPECT_TRUE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("string"), LITERAL(TypedValue()))).IsNull()); + EXPECT_THROW(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("string"), LITERAL(42))), + QueryRuntimeException); + EXPECT_THROW(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("string"), LIST(LITERAL("regex")))), + QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, RegexMatchInvalidRegex) { - EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("*ext"))), QueryRuntimeException); - EXPECT_THROW(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext"))), QueryRuntimeException); +TYPED_TEST(ExpressionEvaluatorTest, RegexMatchInvalidRegex) { + EXPECT_THROW(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL("*ext"))), + QueryRuntimeException); + EXPECT_THROW(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL("[ext"))), + QueryRuntimeException); } -TEST_F(ExpressionEvaluatorTest, RegexMatch) { - EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ex"))).ValueBool()); - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".*ext"))).ValueBool()); - EXPECT_FALSE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL("[ext]"))).ValueBool()); - EXPECT_TRUE(Eval(storage.Create<RegexMatch>(LITERAL("text"), LITERAL(".+[ext]"))).ValueBool()); +TYPED_TEST(ExpressionEvaluatorTest, RegexMatch) { + EXPECT_FALSE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL(".*ex"))).ValueBool()); + EXPECT_TRUE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL(".*ext"))).ValueBool()); + EXPECT_FALSE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL("[ext]"))).ValueBool()); + EXPECT_TRUE(this->Eval(this->storage.template Create<RegexMatch>(LITERAL("text"), LITERAL(".+[ext]"))).ValueBool()); } -class ExpressionEvaluatorPropertyLookup : public ExpressionEvaluatorTest { +template <typename StorageType> +class ExpressionEvaluatorPropertyLookup : public ExpressionEvaluatorTest<StorageType> { protected: - std::pair<std::string, memgraph::storage::PropertyId> prop_age = std::make_pair("age", dba.NameToProperty("age")); + std::pair<std::string, memgraph::storage::PropertyId> prop_age = + std::make_pair("age", this->dba.NameToProperty("age")); std::pair<std::string, memgraph::storage::PropertyId> prop_height = - std::make_pair("height", dba.NameToProperty("height")); - Identifier *identifier = storage.Create<Identifier>("element"); - Symbol symbol = symbol_table.CreateSymbol("element", true); + std::make_pair("height", this->dba.NameToProperty("height")); + Identifier *identifier = this->storage.template Create<Identifier>("element"); + Symbol symbol = this->symbol_table.CreateSymbol("element", true); void SetUp() { identifier->MapTo(symbol); } auto Value(std::pair<std::string, memgraph::storage::PropertyId> property) { - auto *op = storage.Create<PropertyLookup>(identifier, storage.GetPropertyIx(property.first)); - return Eval(op); + auto *op = this->storage.template Create<PropertyLookup>(identifier, this->storage.GetPropertyIx(property.first)); + return this->Eval(op); } }; -TEST_F(ExpressionEvaluatorPropertyLookup, Vertex) { - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); - dba.AdvanceCommand(); - frame[symbol] = TypedValue(v1); - EXPECT_EQ(Value(prop_age).ValueInt(), 10); - EXPECT_TRUE(Value(prop_height).IsNull()); +TYPED_TEST_CASE(ExpressionEvaluatorPropertyLookup, StorageTypes); + +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Vertex) { + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.SetProperty(this->prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); + this->dba.AdvanceCommand(); + this->frame[this->symbol] = TypedValue(v1); + EXPECT_EQ(this->Value(this->prop_age).ValueInt(), 10); + EXPECT_TRUE(this->Value(this->prop_height).IsNull()); } -TEST_F(ExpressionEvaluatorPropertyLookup, Duration) { +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Duration) { const memgraph::utils::Duration dur({10, 1, 30, 2, 22, 45}); - frame[symbol] = TypedValue(dur); + this->frame[this->symbol] = TypedValue(dur); - const std::pair day = std::make_pair("day", dba.NameToProperty("day")); - const auto total_days = Value(day); + const std::pair day = std::make_pair("day", this->dba.NameToProperty("day")); + const auto total_days = this->Value(day); EXPECT_TRUE(total_days.IsInt()); EXPECT_EQ(total_days.ValueInt(), 10); - const std::pair hour = std::make_pair("hour", dba.NameToProperty("hour")); - const auto total_hours = Value(hour); + const std::pair hour = std::make_pair("hour", this->dba.NameToProperty("hour")); + const auto total_hours = this->Value(hour); EXPECT_TRUE(total_hours.IsInt()); EXPECT_EQ(total_hours.ValueInt(), 1); - const std::pair minute = std::make_pair("minute", dba.NameToProperty("minute")); - const auto total_mins = Value(minute); + const std::pair minute = std::make_pair("minute", this->dba.NameToProperty("minute")); + const auto total_mins = this->Value(minute); EXPECT_TRUE(total_mins.IsInt()); EXPECT_EQ(total_mins.ValueInt(), 1 * 60 + 30); - const std::pair sec = std::make_pair("second", dba.NameToProperty("second")); - const auto total_secs = Value(sec); + const std::pair sec = std::make_pair("second", this->dba.NameToProperty("second")); + const auto total_secs = this->Value(sec); EXPECT_TRUE(total_secs.IsInt()); const auto expected_secs = total_mins.ValueInt() * 60 + 2; EXPECT_EQ(total_secs.ValueInt(), expected_secs); - const std::pair milli = std::make_pair("millisecond", dba.NameToProperty("millisecond")); - const auto total_milli = Value(milli); + const std::pair milli = std::make_pair("millisecond", this->dba.NameToProperty("millisecond")); + const auto total_milli = this->Value(milli); EXPECT_TRUE(total_milli.IsInt()); const auto expected_milli = total_secs.ValueInt() * 1000 + 22; EXPECT_EQ(total_milli.ValueInt(), expected_milli); - const std::pair micro = std::make_pair("microsecond", dba.NameToProperty("microsecond")); - const auto total_micros = Value(micro); + const std::pair micro = std::make_pair("microsecond", this->dba.NameToProperty("microsecond")); + const auto total_micros = this->Value(micro); EXPECT_TRUE(total_micros.IsInt()); const auto expected_micros = expected_milli * 1000 + 45; EXPECT_EQ(total_micros.ValueInt(), expected_micros); - const std::pair nano = std::make_pair("nanosecond", dba.NameToProperty("nanosecond")); - const auto total_nano = Value(nano); + const std::pair nano = std::make_pair("nanosecond", this->dba.NameToProperty("nanosecond")); + const auto total_nano = this->Value(nano); EXPECT_TRUE(total_nano.IsInt()); const auto expected_nano = expected_micros * 1000; EXPECT_EQ(total_nano.ValueInt(), expected_nano); } -TEST_F(ExpressionEvaluatorPropertyLookup, Date) { +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Date) { const memgraph::utils::Date date({1996, 11, 22}); - frame[symbol] = TypedValue(date); + this->frame[this->symbol] = TypedValue(date); - const std::pair year = std::make_pair("year", dba.NameToProperty("year")); - const auto y = Value(year); + const std::pair year = std::make_pair("year", this->dba.NameToProperty("year")); + const auto y = this->Value(year); EXPECT_TRUE(y.IsInt()); EXPECT_EQ(y.ValueInt(), 1996); - const std::pair month = std::make_pair("month", dba.NameToProperty("month")); - const auto m = Value(month); + const std::pair month = std::make_pair("month", this->dba.NameToProperty("month")); + const auto m = this->Value(month); EXPECT_TRUE(m.IsInt()); EXPECT_EQ(m.ValueInt(), 11); - const std::pair day = std::make_pair("day", dba.NameToProperty("day")); - const auto d = Value(day); + const std::pair day = std::make_pair("day", this->dba.NameToProperty("day")); + const auto d = this->Value(day); EXPECT_TRUE(d.IsInt()); EXPECT_EQ(d.ValueInt(), 22); } -TEST_F(ExpressionEvaluatorPropertyLookup, LocalTime) { +TYPED_TEST(ExpressionEvaluatorPropertyLookup, LocalTime) { const memgraph::utils::LocalTime lt({1, 2, 3, 11, 22}); - frame[symbol] = TypedValue(lt); + this->frame[this->symbol] = TypedValue(lt); - const std::pair hour = std::make_pair("hour", dba.NameToProperty("hour")); - const auto h = Value(hour); + const std::pair hour = std::make_pair("hour", this->dba.NameToProperty("hour")); + const auto h = this->Value(hour); EXPECT_TRUE(h.IsInt()); EXPECT_EQ(h.ValueInt(), 1); - const std::pair minute = std::make_pair("minute", dba.NameToProperty("minute")); - const auto min = Value(minute); + const std::pair minute = std::make_pair("minute", this->dba.NameToProperty("minute")); + const auto min = this->Value(minute); EXPECT_TRUE(min.IsInt()); EXPECT_EQ(min.ValueInt(), 2); - const std::pair second = std::make_pair("second", dba.NameToProperty("second")); - const auto sec = Value(second); + const std::pair second = std::make_pair("second", this->dba.NameToProperty("second")); + const auto sec = this->Value(second); EXPECT_TRUE(sec.IsInt()); EXPECT_EQ(sec.ValueInt(), 3); - const std::pair millis = std::make_pair("millisecond", dba.NameToProperty("millisecond")); - const auto mil = Value(millis); + const std::pair millis = std::make_pair("millisecond", this->dba.NameToProperty("millisecond")); + const auto mil = this->Value(millis); EXPECT_TRUE(mil.IsInt()); EXPECT_EQ(mil.ValueInt(), 11); - const std::pair micros = std::make_pair("microsecond", dba.NameToProperty("microsecond")); - const auto mic = Value(micros); + const std::pair micros = std::make_pair("microsecond", this->dba.NameToProperty("microsecond")); + const auto mic = this->Value(micros); EXPECT_TRUE(mic.IsInt()); EXPECT_EQ(mic.ValueInt(), 22); } -TEST_F(ExpressionEvaluatorPropertyLookup, LocalDateTime) { +TYPED_TEST(ExpressionEvaluatorPropertyLookup, LocalDateTime) { const memgraph::utils::LocalDateTime ldt({1993, 8, 6}, {2, 3, 4, 55, 40}); - frame[symbol] = TypedValue(ldt); + this->frame[this->symbol] = TypedValue(ldt); - const std::pair year = std::make_pair("year", dba.NameToProperty("year")); - const auto y = Value(year); + const std::pair year = std::make_pair("year", this->dba.NameToProperty("year")); + const auto y = this->Value(year); EXPECT_TRUE(y.IsInt()); EXPECT_EQ(y.ValueInt(), 1993); - const std::pair month = std::make_pair("month", dba.NameToProperty("month")); - const auto m = Value(month); + const std::pair month = std::make_pair("month", this->dba.NameToProperty("month")); + const auto m = this->Value(month); EXPECT_TRUE(m.IsInt()); EXPECT_EQ(m.ValueInt(), 8); - const std::pair day = std::make_pair("day", dba.NameToProperty("day")); - const auto d = Value(day); + const std::pair day = std::make_pair("day", this->dba.NameToProperty("day")); + const auto d = this->Value(day); EXPECT_TRUE(d.IsInt()); EXPECT_EQ(d.ValueInt(), 6); - const std::pair hour = std::make_pair("hour", dba.NameToProperty("hour")); - const auto h = Value(hour); + const std::pair hour = std::make_pair("hour", this->dba.NameToProperty("hour")); + const auto h = this->Value(hour); EXPECT_TRUE(h.IsInt()); EXPECT_EQ(h.ValueInt(), 2); - const std::pair minute = std::make_pair("minute", dba.NameToProperty("minute")); - const auto min = Value(minute); + const std::pair minute = std::make_pair("minute", this->dba.NameToProperty("minute")); + const auto min = this->Value(minute); EXPECT_TRUE(min.IsInt()); EXPECT_EQ(min.ValueInt(), 3); - const std::pair second = std::make_pair("second", dba.NameToProperty("second")); - const auto sec = Value(second); + const std::pair second = std::make_pair("second", this->dba.NameToProperty("second")); + const auto sec = this->Value(second); EXPECT_TRUE(sec.IsInt()); EXPECT_EQ(sec.ValueInt(), 4); - const std::pair millis = std::make_pair("millisecond", dba.NameToProperty("millisecond")); - const auto mil = Value(millis); + const std::pair millis = std::make_pair("millisecond", this->dba.NameToProperty("millisecond")); + const auto mil = this->Value(millis); EXPECT_TRUE(mil.IsInt()); EXPECT_EQ(mil.ValueInt(), 55); - const std::pair micros = std::make_pair("microsecond", dba.NameToProperty("microsecond")); - const auto mic = Value(micros); + const std::pair micros = std::make_pair("microsecond", this->dba.NameToProperty("microsecond")); + const auto mic = this->Value(micros); EXPECT_TRUE(mic.IsInt()); EXPECT_EQ(mic.ValueInt(), 40); } -TEST_F(ExpressionEvaluatorPropertyLookup, Edge) { - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Edge) { + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto e12 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("edge_type")); ASSERT_TRUE(e12.HasValue()); - ASSERT_TRUE(e12->SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); - dba.AdvanceCommand(); - frame[symbol] = TypedValue(*e12); - EXPECT_EQ(Value(prop_age).ValueInt(), 10); - EXPECT_TRUE(Value(prop_height).IsNull()); + ASSERT_TRUE(e12->SetProperty(this->prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); + this->dba.AdvanceCommand(); + this->frame[this->symbol] = TypedValue(*e12); + EXPECT_EQ(this->Value(this->prop_age).ValueInt(), 10); + EXPECT_TRUE(this->Value(this->prop_height).IsNull()); } -TEST_F(ExpressionEvaluatorPropertyLookup, Null) { - frame[symbol] = TypedValue(); - EXPECT_TRUE(Value(prop_age).IsNull()); +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Null) { + this->frame[this->symbol] = TypedValue(); + EXPECT_TRUE(this->Value(this->prop_age).IsNull()); } -TEST_F(ExpressionEvaluatorPropertyLookup, Map) { - frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}}); - EXPECT_EQ(Value(prop_age).ValueInt(), 10); - EXPECT_TRUE(Value(prop_height).IsNull()); +TYPED_TEST(ExpressionEvaluatorPropertyLookup, Map) { + this->frame[this->symbol] = TypedValue(std::map<std::string, TypedValue>{{this->prop_age.first, TypedValue(10)}}); + EXPECT_EQ(this->Value(this->prop_age).ValueInt(), 10); + EXPECT_TRUE(this->Value(this->prop_height).IsNull()); } -class ExpressionEvaluatorAllPropertiesLookup : public ExpressionEvaluatorTest { +template <typename StorageType> +class ExpressionEvaluatorAllPropertiesLookup : public ExpressionEvaluatorTest<StorageType> { protected: - std::pair<std::string, memgraph::storage::PropertyId> prop_age = std::make_pair("age", dba.NameToProperty("age")); + std::pair<std::string, memgraph::storage::PropertyId> prop_age = + std::make_pair("age", this->dba.NameToProperty("age")); std::pair<std::string, memgraph::storage::PropertyId> prop_height = - std::make_pair("height", dba.NameToProperty("height")); - Identifier *identifier = storage.Create<Identifier>("element"); - Symbol symbol = symbol_table.CreateSymbol("element", true); + std::make_pair("height", this->dba.NameToProperty("height")); + Identifier *identifier = this->storage.template Create<Identifier>("element"); + Symbol symbol = this->symbol_table.CreateSymbol("element", true); void SetUp() { identifier->MapTo(symbol); } auto Value() { - auto *op = storage.Create<AllPropertiesLookup>(identifier); - return Eval(op); + auto *op = this->storage.template Create<AllPropertiesLookup>(identifier); + return this->Eval(op); } }; -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Vertex) { - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); - dba.AdvanceCommand(); - frame[symbol] = TypedValue(v1); - auto all_properties = Value(); +TYPED_TEST_CASE(ExpressionEvaluatorAllPropertiesLookup, StorageTypes); + +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Vertex) { + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.SetProperty(this->prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); + this->dba.AdvanceCommand(); + this->frame[this->symbol] = TypedValue(v1); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Edge) { - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Edge) { + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto e12 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("edge_type")); ASSERT_TRUE(e12.HasValue()); - ASSERT_TRUE(e12->SetProperty(prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); - dba.AdvanceCommand(); - frame[symbol] = TypedValue(*e12); - auto all_properties = Value(); + ASSERT_TRUE(e12->SetProperty(this->prop_age.second, memgraph::storage::PropertyValue(10)).HasValue()); + this->dba.AdvanceCommand(); + this->frame[this->symbol] = TypedValue(*e12); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Duration) { +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Duration) { const memgraph::utils::Duration dur({10, 1, 30, 2, 22, 45}); - frame[symbol] = TypedValue(dur); - auto all_properties = Value(); + this->frame[this->symbol] = TypedValue(dur); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Date) { +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Date) { const memgraph::utils::Date date({1996, 11, 22}); - frame[symbol] = TypedValue(date); - auto all_properties = Value(); + this->frame[this->symbol] = TypedValue(date); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, LocalTime) { +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, LocalTime) { const memgraph::utils::LocalTime lt({1, 2, 3, 11, 22}); - frame[symbol] = TypedValue(lt); - auto all_properties = Value(); + this->frame[this->symbol] = TypedValue(lt); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, LocalDateTime) { +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, LocalDateTime) { const memgraph::utils::LocalDateTime ldt({1993, 8, 6}, {2, 3, 4, 55, 40}); - frame[symbol] = TypedValue(ldt); - auto all_properties = Value(); + this->frame[this->symbol] = TypedValue(ldt); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Null) { - frame[symbol] = TypedValue(); - auto all_properties = Value(); +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Null) { + this->frame[this->symbol] = TypedValue(); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsNull()); } -TEST_F(ExpressionEvaluatorAllPropertiesLookup, Map) { - frame[symbol] = TypedValue(std::map<std::string, TypedValue>{{prop_age.first, TypedValue(10)}}); - auto all_properties = Value(); +TYPED_TEST(ExpressionEvaluatorAllPropertiesLookup, Map) { + this->frame[this->symbol] = TypedValue(std::map<std::string, TypedValue>{{this->prop_age.first, TypedValue(10)}}); + auto all_properties = this->Value(); EXPECT_TRUE(all_properties.IsMap()); } -class FunctionTest : public ExpressionEvaluatorTest { +template <typename StorageType> +class FunctionTest : public ExpressionEvaluatorTest<StorageType> { protected: std::vector<Expression *> ExpressionsFromTypedValues(const std::vector<TypedValue> &tvs) { std::vector<Expression *> expressions; expressions.reserve(tvs.size()); for (size_t i = 0; i < tvs.size(); ++i) { - auto *ident = storage.Create<Identifier>("arg_" + std::to_string(i), true); - auto sym = symbol_table.CreateSymbol("arg_" + std::to_string(i), true); + auto *ident = this->storage.template Create<Identifier>("arg_" + std::to_string(i), true); + auto sym = this->symbol_table.CreateSymbol("arg_" + std::to_string(i), true); ident->MapTo(sym); - frame[sym] = tvs[i]; + this->frame[sym] = tvs[i]; expressions.push_back(ident); } @@ -1373,8 +1478,8 @@ class FunctionTest : public ExpressionEvaluatorTest { } TypedValue EvaluateFunctionWithExprs(const std::string &function_name, const std::vector<Expression *> &expressions) { - auto *op = storage.Create<Function>(function_name, expressions); - return Eval(op); + auto *op = this->storage.template Create<Function>(function_name, expressions); + return this->Eval(op); } template <class... TArgs> @@ -1392,47 +1497,50 @@ class FunctionTest : public ExpressionEvaluatorTest { } }; +TYPED_TEST_CASE(FunctionTest, StorageTypes); + template <class... TArgs> static TypedValue MakeTypedValueList(TArgs &&...args) { return TypedValue(std::vector<TypedValue>{TypedValue(args)...}); } -TEST_F(FunctionTest, EndNode) { - ASSERT_THROW(EvaluateFunction("ENDNODE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("ENDNODE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("label1")).HasValue()); - auto v2 = dba.InsertVertex(); - ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("label2")).HasValue()); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("t")); +TYPED_TEST(FunctionTest, EndNode) { + ASSERT_THROW(this->EvaluateFunction("ENDNODE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("ENDNODE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("label1")).HasValue()); + auto v2 = this->dba.InsertVertex(); + ASSERT_TRUE(v2.AddLabel(this->dba.NameToLabel("label2")).HasValue()); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("t")); ASSERT_TRUE(e.HasValue()); - ASSERT_TRUE( - *EvaluateFunction("ENDNODE", *e).ValueVertex().HasLabel(memgraph::storage::View::NEW, dba.NameToLabel("label2"))); - ASSERT_THROW(EvaluateFunction("ENDNODE", 2), QueryRuntimeException); + ASSERT_TRUE(*this->EvaluateFunction("ENDNODE", *e) + .ValueVertex() + .HasLabel(memgraph::storage::View::NEW, this->dba.NameToLabel("label2"))); + ASSERT_THROW(this->EvaluateFunction("ENDNODE", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Head) { - ASSERT_THROW(EvaluateFunction("HEAD"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("HEAD", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, Head) { + ASSERT_THROW(this->EvaluateFunction("HEAD"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("HEAD", TypedValue()).IsNull()); auto argument = MakeTypedValueList(3, 4, 5); - ASSERT_EQ(EvaluateFunction("HEAD", argument).ValueInt(), 3); + ASSERT_EQ(this->EvaluateFunction("HEAD", argument).ValueInt(), 3); argument.ValueList().clear(); - ASSERT_TRUE(EvaluateFunction("HEAD", argument).IsNull()); - ASSERT_THROW(EvaluateFunction("HEAD", 2), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("HEAD", argument).IsNull()); + ASSERT_THROW(this->EvaluateFunction("HEAD", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Properties) { - ASSERT_THROW(EvaluateFunction("PROPERTIES"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("PROPERTIES", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("height"), memgraph::storage::PropertyValue(5)).HasValue()); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("age"), memgraph::storage::PropertyValue(10)).HasValue()); - auto v2 = dba.InsertVertex(); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("type1")); +TYPED_TEST(FunctionTest, Properties) { + ASSERT_THROW(this->EvaluateFunction("PROPERTIES"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("PROPERTIES", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.SetProperty(this->dba.NameToProperty("height"), memgraph::storage::PropertyValue(5)).HasValue()); + ASSERT_TRUE(v1.SetProperty(this->dba.NameToProperty("age"), memgraph::storage::PropertyValue(10)).HasValue()); + auto v2 = this->dba.InsertVertex(); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("type1")); ASSERT_TRUE(e.HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("height"), memgraph::storage::PropertyValue(3)).HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("age"), memgraph::storage::PropertyValue(15)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(e->SetProperty(this->dba.NameToProperty("height"), memgraph::storage::PropertyValue(3)).HasValue()); + ASSERT_TRUE(e->SetProperty(this->dba.NameToProperty("age"), memgraph::storage::PropertyValue(15)).HasValue()); + this->dba.AdvanceCommand(); auto prop_values_to_int = [](TypedValue t) { std::unordered_map<std::string, int> properties; @@ -1442,228 +1550,228 @@ TEST_F(FunctionTest, Properties) { return properties; }; - ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", v1)), + ASSERT_THAT(prop_values_to_int(this->EvaluateFunction("PROPERTIES", v1)), UnorderedElementsAre(testing::Pair("height", 5), testing::Pair("age", 10))); - ASSERT_THAT(prop_values_to_int(EvaluateFunction("PROPERTIES", *e)), + ASSERT_THAT(prop_values_to_int(this->EvaluateFunction("PROPERTIES", *e)), UnorderedElementsAre(testing::Pair("height", 3), testing::Pair("age", 15))); - ASSERT_THROW(EvaluateFunction("PROPERTIES", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("PROPERTIES", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Last) { - ASSERT_THROW(EvaluateFunction("LAST"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("LAST", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, Last) { + ASSERT_THROW(this->EvaluateFunction("LAST"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("LAST", TypedValue()).IsNull()); auto argument = MakeTypedValueList(3, 4, 5); - ASSERT_EQ(EvaluateFunction("LAST", argument).ValueInt(), 5); + ASSERT_EQ(this->EvaluateFunction("LAST", argument).ValueInt(), 5); argument.ValueList().clear(); - ASSERT_TRUE(EvaluateFunction("LAST", argument).IsNull()); - ASSERT_THROW(EvaluateFunction("LAST", 5), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("LAST", argument).IsNull()); + ASSERT_THROW(this->EvaluateFunction("LAST", 5), QueryRuntimeException); } -TEST_F(FunctionTest, Size) { - ASSERT_THROW(EvaluateFunction("SIZE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("SIZE", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, Size) { + ASSERT_THROW(this->EvaluateFunction("SIZE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("SIZE", TypedValue()).IsNull()); auto argument = MakeTypedValueList(3, 4, 5); - ASSERT_EQ(EvaluateFunction("SIZE", argument).ValueInt(), 3); - ASSERT_EQ(EvaluateFunction("SIZE", "john").ValueInt(), 4); - ASSERT_EQ(EvaluateFunction("SIZE", - std::map<std::string, TypedValue>{ - {"a", TypedValue(5)}, {"b", TypedValue(true)}, {"c", TypedValue("123")}}) + ASSERT_EQ(this->EvaluateFunction("SIZE", argument).ValueInt(), 3); + ASSERT_EQ(this->EvaluateFunction("SIZE", "john").ValueInt(), 4); + ASSERT_EQ(this->EvaluateFunction("SIZE", + std::map<std::string, TypedValue>{ + {"a", TypedValue(5)}, {"b", TypedValue(true)}, {"c", TypedValue("123")}}) .ValueInt(), 3); - ASSERT_THROW(EvaluateFunction("SIZE", 5), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("SIZE", 5), QueryRuntimeException); - auto v0 = dba.InsertVertex(); + auto v0 = this->dba.InsertVertex(); memgraph::query::Path path(v0); - EXPECT_EQ(EvaluateFunction("SIZE", path).ValueInt(), 0); - auto v1 = dba.InsertVertex(); - auto edge = dba.InsertEdge(&v0, &v1, dba.NameToEdgeType("type")); + EXPECT_EQ(this->EvaluateFunction("SIZE", path).ValueInt(), 0); + auto v1 = this->dba.InsertVertex(); + auto edge = this->dba.InsertEdge(&v0, &v1, this->dba.NameToEdgeType("type")); ASSERT_TRUE(edge.HasValue()); path.Expand(*edge); path.Expand(v1); - EXPECT_EQ(EvaluateFunction("SIZE", path).ValueInt(), 1); + EXPECT_EQ(this->EvaluateFunction("SIZE", path).ValueInt(), 1); } -TEST_F(FunctionTest, StartNode) { - ASSERT_THROW(EvaluateFunction("STARTNODE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("STARTNODE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("label1")).HasValue()); - auto v2 = dba.InsertVertex(); - ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("label2")).HasValue()); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("t")); +TYPED_TEST(FunctionTest, StartNode) { + ASSERT_THROW(this->EvaluateFunction("STARTNODE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("STARTNODE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("label1")).HasValue()); + auto v2 = this->dba.InsertVertex(); + ASSERT_TRUE(v2.AddLabel(this->dba.NameToLabel("label2")).HasValue()); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("t")); ASSERT_TRUE(e.HasValue()); - ASSERT_TRUE(*EvaluateFunction("STARTNODE", *e) + ASSERT_TRUE(*this->EvaluateFunction("STARTNODE", *e) .ValueVertex() - .HasLabel(memgraph::storage::View::NEW, dba.NameToLabel("label1"))); - ASSERT_THROW(EvaluateFunction("STARTNODE", 2), QueryRuntimeException); + .HasLabel(memgraph::storage::View::NEW, this->dba.NameToLabel("label1"))); + ASSERT_THROW(this->EvaluateFunction("STARTNODE", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Degree) { - ASSERT_THROW(EvaluateFunction("DEGREE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("DEGREE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto v3 = dba.InsertVertex(); - auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("t")); +TYPED_TEST(FunctionTest, Degree) { + ASSERT_THROW(this->EvaluateFunction("DEGREE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("DEGREE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto v3 = this->dba.InsertVertex(); + auto e12 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("t")); ASSERT_TRUE(e12.HasValue()); - ASSERT_TRUE(dba.InsertEdge(&v3, &v2, dba.NameToEdgeType("t")).HasValue()); - dba.AdvanceCommand(); - ASSERT_EQ(EvaluateFunction("DEGREE", v1).ValueInt(), 1); - ASSERT_EQ(EvaluateFunction("DEGREE", v2).ValueInt(), 2); - ASSERT_EQ(EvaluateFunction("DEGREE", v3).ValueInt(), 1); - ASSERT_THROW(EvaluateFunction("DEGREE", 2), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("DEGREE", *e12), QueryRuntimeException); + ASSERT_TRUE(this->dba.InsertEdge(&v3, &v2, this->dba.NameToEdgeType("t")).HasValue()); + this->dba.AdvanceCommand(); + ASSERT_EQ(this->EvaluateFunction("DEGREE", v1).ValueInt(), 1); + ASSERT_EQ(this->EvaluateFunction("DEGREE", v2).ValueInt(), 2); + ASSERT_EQ(this->EvaluateFunction("DEGREE", v3).ValueInt(), 1); + ASSERT_THROW(this->EvaluateFunction("DEGREE", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("DEGREE", *e12), QueryRuntimeException); } -TEST_F(FunctionTest, InDegree) { - ASSERT_THROW(EvaluateFunction("INDEGREE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("INDEGREE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto v3 = dba.InsertVertex(); - auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("t")); +TYPED_TEST(FunctionTest, InDegree) { + ASSERT_THROW(this->EvaluateFunction("INDEGREE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("INDEGREE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto v3 = this->dba.InsertVertex(); + auto e12 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("t")); ASSERT_TRUE(e12.HasValue()); - ASSERT_TRUE(dba.InsertEdge(&v3, &v2, dba.NameToEdgeType("t")).HasValue()); - dba.AdvanceCommand(); - ASSERT_EQ(EvaluateFunction("INDEGREE", v1).ValueInt(), 0); - ASSERT_EQ(EvaluateFunction("INDEGREE", v2).ValueInt(), 2); - ASSERT_EQ(EvaluateFunction("INDEGREE", v3).ValueInt(), 0); - ASSERT_THROW(EvaluateFunction("INDEGREE", 2), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("INDEGREE", *e12), QueryRuntimeException); + ASSERT_TRUE(this->dba.InsertEdge(&v3, &v2, this->dba.NameToEdgeType("t")).HasValue()); + this->dba.AdvanceCommand(); + ASSERT_EQ(this->EvaluateFunction("INDEGREE", v1).ValueInt(), 0); + ASSERT_EQ(this->EvaluateFunction("INDEGREE", v2).ValueInt(), 2); + ASSERT_EQ(this->EvaluateFunction("INDEGREE", v3).ValueInt(), 0); + ASSERT_THROW(this->EvaluateFunction("INDEGREE", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("INDEGREE", *e12), QueryRuntimeException); } -TEST_F(FunctionTest, OutDegree) { - ASSERT_THROW(EvaluateFunction("OUTDEGREE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("OUTDEGREE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto v3 = dba.InsertVertex(); - auto e12 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("t")); +TYPED_TEST(FunctionTest, OutDegree) { + ASSERT_THROW(this->EvaluateFunction("OUTDEGREE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("OUTDEGREE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto v3 = this->dba.InsertVertex(); + auto e12 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("t")); ASSERT_TRUE(e12.HasValue()); - ASSERT_TRUE(dba.InsertEdge(&v3, &v2, dba.NameToEdgeType("t")).HasValue()); - dba.AdvanceCommand(); - ASSERT_EQ(EvaluateFunction("OUTDEGREE", v1).ValueInt(), 1); - ASSERT_EQ(EvaluateFunction("OUTDEGREE", v2).ValueInt(), 0); - ASSERT_EQ(EvaluateFunction("OUTDEGREE", v3).ValueInt(), 1); - ASSERT_THROW(EvaluateFunction("OUTDEGREE", 2), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("OUTDEGREE", *e12), QueryRuntimeException); + ASSERT_TRUE(this->dba.InsertEdge(&v3, &v2, this->dba.NameToEdgeType("t")).HasValue()); + this->dba.AdvanceCommand(); + ASSERT_EQ(this->EvaluateFunction("OUTDEGREE", v1).ValueInt(), 1); + ASSERT_EQ(this->EvaluateFunction("OUTDEGREE", v2).ValueInt(), 0); + ASSERT_EQ(this->EvaluateFunction("OUTDEGREE", v3).ValueInt(), 1); + ASSERT_THROW(this->EvaluateFunction("OUTDEGREE", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("OUTDEGREE", *e12), QueryRuntimeException); } -TEST_F(FunctionTest, ToBoolean) { - ASSERT_THROW(EvaluateFunction("TOBOOLEAN"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 123).ValueBool(), true); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", -213).ValueBool(), true); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", 0).ValueBool(), false); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", " trUE \n\t").ValueBool(), true); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", "\n\tFalsE").ValueBool(), false); - ASSERT_TRUE(EvaluateFunction("TOBOOLEAN", "\n\tFALSEA ").IsNull()); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", true).ValueBool(), true); - ASSERT_EQ(EvaluateFunction("TOBOOLEAN", false).ValueBool(), false); +TYPED_TEST(FunctionTest, ToBoolean) { + ASSERT_THROW(this->EvaluateFunction("TOBOOLEAN"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("TOBOOLEAN", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", 123).ValueBool(), true); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", -213).ValueBool(), true); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", 0).ValueBool(), false); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", " trUE \n\t").ValueBool(), true); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", "\n\tFalsE").ValueBool(), false); + ASSERT_TRUE(this->EvaluateFunction("TOBOOLEAN", "\n\tFALSEA ").IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", true).ValueBool(), true); + ASSERT_EQ(this->EvaluateFunction("TOBOOLEAN", false).ValueBool(), false); } -TEST_F(FunctionTest, ToFloat) { - ASSERT_THROW(EvaluateFunction("TOFLOAT"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("TOFLOAT", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("TOFLOAT", " -3.5 \n\t").ValueDouble(), -3.5); - ASSERT_EQ(EvaluateFunction("TOFLOAT", "\n\t0.5e-1").ValueDouble(), 0.05); - ASSERT_TRUE(EvaluateFunction("TOFLOAT", "\n\t3.4e-3X ").IsNull()); - ASSERT_EQ(EvaluateFunction("TOFLOAT", -3.5).ValueDouble(), -3.5); - ASSERT_EQ(EvaluateFunction("TOFLOAT", -3).ValueDouble(), -3.0); - ASSERT_THROW(EvaluateFunction("TOFLOAT", true), QueryRuntimeException); +TYPED_TEST(FunctionTest, ToFloat) { + ASSERT_THROW(this->EvaluateFunction("TOFLOAT"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("TOFLOAT", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOFLOAT", " -3.5 \n\t").ValueDouble(), -3.5); + ASSERT_EQ(this->EvaluateFunction("TOFLOAT", "\n\t0.5e-1").ValueDouble(), 0.05); + ASSERT_TRUE(this->EvaluateFunction("TOFLOAT", "\n\t3.4e-3X ").IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOFLOAT", -3.5).ValueDouble(), -3.5); + ASSERT_EQ(this->EvaluateFunction("TOFLOAT", -3).ValueDouble(), -3.0); + ASSERT_THROW(this->EvaluateFunction("TOFLOAT", true), QueryRuntimeException); } -TEST_F(FunctionTest, ToInteger) { - ASSERT_THROW(EvaluateFunction("TOINTEGER"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("TOINTEGER", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("TOINTEGER", false).ValueInt(), 0); - ASSERT_EQ(EvaluateFunction("TOINTEGER", true).ValueInt(), 1); - ASSERT_EQ(EvaluateFunction("TOINTEGER", "\n\t3").ValueInt(), 3); - ASSERT_EQ(EvaluateFunction("TOINTEGER", " -3.5 \n\t").ValueInt(), -3); - ASSERT_TRUE(EvaluateFunction("TOINTEGER", "\n\t3X ").IsNull()); - ASSERT_EQ(EvaluateFunction("TOINTEGER", -3.5).ValueInt(), -3); - ASSERT_EQ(EvaluateFunction("TOINTEGER", 3.5).ValueInt(), 3); +TYPED_TEST(FunctionTest, ToInteger) { + ASSERT_THROW(this->EvaluateFunction("TOINTEGER"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("TOINTEGER", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", false).ValueInt(), 0); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", true).ValueInt(), 1); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", "\n\t3").ValueInt(), 3); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", " -3.5 \n\t").ValueInt(), -3); + ASSERT_TRUE(this->EvaluateFunction("TOINTEGER", "\n\t3X ").IsNull()); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", -3.5).ValueInt(), -3); + ASSERT_EQ(this->EvaluateFunction("TOINTEGER", 3.5).ValueInt(), 3); } -TEST_F(FunctionTest, Type) { - ASSERT_THROW(EvaluateFunction("TYPE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("TYPE", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("label1")).HasValue()); - auto v2 = dba.InsertVertex(); - ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("label2")).HasValue()); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("type1")); +TYPED_TEST(FunctionTest, Type) { + ASSERT_THROW(this->EvaluateFunction("TYPE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("TYPE", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(this->dba.NameToLabel("label1")).HasValue()); + auto v2 = this->dba.InsertVertex(); + ASSERT_TRUE(v2.AddLabel(this->dba.NameToLabel("label2")).HasValue()); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("type1")); ASSERT_TRUE(e.HasValue()); - ASSERT_EQ(EvaluateFunction("TYPE", *e).ValueString(), "type1"); - ASSERT_THROW(EvaluateFunction("TYPE", 2), QueryRuntimeException); + ASSERT_EQ(this->EvaluateFunction("TYPE", *e).ValueString(), "type1"); + ASSERT_THROW(this->EvaluateFunction("TYPE", 2), QueryRuntimeException); } -TEST_F(FunctionTest, ValueType) { - ASSERT_THROW(EvaluateFunction("VALUETYPE"), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("VALUETYPE", TypedValue(), TypedValue()), QueryRuntimeException); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue()).ValueString(), "NULL"); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(true)).ValueString(), "BOOLEAN"); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1)).ValueString(), "INTEGER"); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1.1)).ValueString(), "FLOAT"); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue("test")).ValueString(), "STRING"); - ASSERT_EQ( - EvaluateFunction("VALUETYPE", TypedValue(std::vector<TypedValue>{TypedValue(1), TypedValue(2)})).ValueString(), - "LIST"); - ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(std::map<std::string, TypedValue>{{"test", TypedValue(1)}})) +TYPED_TEST(FunctionTest, ValueType) { + ASSERT_THROW(this->EvaluateFunction("VALUETYPE"), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("VALUETYPE", TypedValue(), TypedValue()), QueryRuntimeException); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue()).ValueString(), "NULL"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue(true)).ValueString(), "BOOLEAN"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue(1)).ValueString(), "INTEGER"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue(1.1)).ValueString(), "FLOAT"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue("test")).ValueString(), "STRING"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue(std::vector<TypedValue>{TypedValue(1), TypedValue(2)})) + .ValueString(), + "LIST"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", TypedValue(std::map<std::string, TypedValue>{{"test", TypedValue(1)}})) .ValueString(), "MAP"); - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - ASSERT_EQ(EvaluateFunction("VALUETYPE", v1).ValueString(), "NODE"); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("type1")); + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", v1).ValueString(), "NODE"); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("type1")); ASSERT_TRUE(e.HasValue()); - ASSERT_EQ(EvaluateFunction("VALUETYPE", *e).ValueString(), "RELATIONSHIP"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", *e).ValueString(), "RELATIONSHIP"); Path p(v1, *e, v2); - ASSERT_EQ(EvaluateFunction("VALUETYPE", p).ValueString(), "PATH"); + ASSERT_EQ(this->EvaluateFunction("VALUETYPE", p).ValueString(), "PATH"); } -TEST_F(FunctionTest, Labels) { - ASSERT_THROW(EvaluateFunction("LABELS"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("LABELS", TypedValue()).IsNull()); - auto v = dba.InsertVertex(); - ASSERT_TRUE(v.AddLabel(dba.NameToLabel("label1")).HasValue()); - ASSERT_TRUE(v.AddLabel(dba.NameToLabel("label2")).HasValue()); - dba.AdvanceCommand(); +TYPED_TEST(FunctionTest, Labels) { + ASSERT_THROW(this->EvaluateFunction("LABELS"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("LABELS", TypedValue()).IsNull()); + auto v = this->dba.InsertVertex(); + ASSERT_TRUE(v.AddLabel(this->dba.NameToLabel("label1")).HasValue()); + ASSERT_TRUE(v.AddLabel(this->dba.NameToLabel("label2")).HasValue()); + this->dba.AdvanceCommand(); std::vector<std::string> labels; - auto _labels = EvaluateFunction("LABELS", v).ValueList(); + auto _labels = this->EvaluateFunction("LABELS", v).ValueList(); labels.reserve(_labels.size()); for (auto label : _labels) { labels.emplace_back(label.ValueString()); } ASSERT_THAT(labels, UnorderedElementsAre("label1", "label2")); - ASSERT_THROW(EvaluateFunction("LABELS", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("LABELS", 2), QueryRuntimeException); } -TEST_F(FunctionTest, NodesRelationships) { - EXPECT_THROW(EvaluateFunction("NODES"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RELATIONSHIPS"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("NODES", TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("RELATIONSHIPS", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, NodesRelationships) { + EXPECT_THROW(this->EvaluateFunction("NODES"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RELATIONSHIPS"), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("NODES", TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("RELATIONSHIPS", TypedValue()).IsNull()); { - auto v1 = dba.InsertVertex(); - auto v2 = dba.InsertVertex(); - auto v3 = dba.InsertVertex(); - auto e1 = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("Type")); + auto v1 = this->dba.InsertVertex(); + auto v2 = this->dba.InsertVertex(); + auto v3 = this->dba.InsertVertex(); + auto e1 = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("Type")); ASSERT_TRUE(e1.HasValue()); - auto e2 = dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("Type")); + auto e2 = this->dba.InsertEdge(&v2, &v3, this->dba.NameToEdgeType("Type")); ASSERT_TRUE(e2.HasValue()); memgraph::query::Path path{v1, *e1, v2, *e2, v3}; - dba.AdvanceCommand(); + this->dba.AdvanceCommand(); - auto _nodes = EvaluateFunction("NODES", path).ValueList(); + auto _nodes = this->EvaluateFunction("NODES", path).ValueList(); std::vector<memgraph::query::VertexAccessor> nodes; for (const auto &node : _nodes) { nodes.push_back(node.ValueVertex()); } EXPECT_THAT(nodes, ElementsAre(v1, v2, v3)); - auto _edges = EvaluateFunction("RELATIONSHIPS", path).ValueList(); + auto _edges = this->EvaluateFunction("RELATIONSHIPS", path).ValueList(); std::vector<memgraph::query::EdgeAccessor> edges; for (const auto &edge : _edges) { edges.push_back(edge.ValueEdge()); @@ -1671,38 +1779,38 @@ TEST_F(FunctionTest, NodesRelationships) { EXPECT_THAT(edges, ElementsAre(*e1, *e2)); } - EXPECT_THROW(EvaluateFunction("NODES", 2), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RELATIONSHIPS", 2), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("NODES", 2), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RELATIONSHIPS", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Range) { - EXPECT_THROW(EvaluateFunction("RANGE"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("RANGE", 1, 2, TypedValue()).IsNull()); - EXPECT_THROW(EvaluateFunction("RANGE", 1, TypedValue(), 1.3), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RANGE", 1, 2, 0), QueryRuntimeException); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 1, 3)), ElementsAre(1, 2, 3)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -1, 5, 2)), ElementsAre(-1, 1, 3, 5)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 10, 3)), ElementsAre(2, 5, 8)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, 2)), ElementsAre(2)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 3, 0, 5)), ElementsAre()); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 5, 1, -2)), ElementsAre(5, 3, 1)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 6, 1, -2)), ElementsAre(6, 4, 2)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", 2, 2, -3)), ElementsAre(2)); - EXPECT_THAT(ToIntList(EvaluateFunction("RANGE", -2, 4, -1)), ElementsAre()); +TYPED_TEST(FunctionTest, Range) { + EXPECT_THROW(this->EvaluateFunction("RANGE"), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("RANGE", 1, 2, TypedValue()).IsNull()); + EXPECT_THROW(this->EvaluateFunction("RANGE", 1, TypedValue(), 1.3), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RANGE", 1, 2, 0), QueryRuntimeException); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 1, 3)), ElementsAre(1, 2, 3)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", -1, 5, 2)), ElementsAre(-1, 1, 3, 5)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 2, 10, 3)), ElementsAre(2, 5, 8)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 2, 2, 2)), ElementsAre(2)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 3, 0, 5)), ElementsAre()); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 5, 1, -2)), ElementsAre(5, 3, 1)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 6, 1, -2)), ElementsAre(6, 4, 2)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", 2, 2, -3)), ElementsAre(2)); + EXPECT_THAT(ToIntList(this->EvaluateFunction("RANGE", -2, 4, -1)), ElementsAre()); } -TEST_F(FunctionTest, Keys) { - ASSERT_THROW(EvaluateFunction("KEYS"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("KEYS", TypedValue()).IsNull()); - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("height"), memgraph::storage::PropertyValue(5)).HasValue()); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("age"), memgraph::storage::PropertyValue(10)).HasValue()); - auto v2 = dba.InsertVertex(); - auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("type1")); +TYPED_TEST(FunctionTest, Keys) { + ASSERT_THROW(this->EvaluateFunction("KEYS"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("KEYS", TypedValue()).IsNull()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.SetProperty(this->dba.NameToProperty("height"), memgraph::storage::PropertyValue(5)).HasValue()); + ASSERT_TRUE(v1.SetProperty(this->dba.NameToProperty("age"), memgraph::storage::PropertyValue(10)).HasValue()); + auto v2 = this->dba.InsertVertex(); + auto e = this->dba.InsertEdge(&v1, &v2, this->dba.NameToEdgeType("type1")); ASSERT_TRUE(e.HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("width"), memgraph::storage::PropertyValue(3)).HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("age"), memgraph::storage::PropertyValue(15)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(e->SetProperty(this->dba.NameToProperty("width"), memgraph::storage::PropertyValue(3)).HasValue()); + ASSERT_TRUE(e->SetProperty(this->dba.NameToProperty("age"), memgraph::storage::PropertyValue(15)).HasValue()); + this->dba.AdvanceCommand(); auto prop_keys_to_string = [](TypedValue t) { std::vector<std::string> keys; @@ -1711,474 +1819,478 @@ TEST_F(FunctionTest, Keys) { } return keys; }; - ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", v1)), UnorderedElementsAre("height", "age")); - ASSERT_THAT(prop_keys_to_string(EvaluateFunction("KEYS", *e)), UnorderedElementsAre("width", "age")); - ASSERT_THROW(EvaluateFunction("KEYS", 2), QueryRuntimeException); + ASSERT_THAT(prop_keys_to_string(this->EvaluateFunction("KEYS", v1)), UnorderedElementsAre("height", "age")); + ASSERT_THAT(prop_keys_to_string(this->EvaluateFunction("KEYS", *e)), UnorderedElementsAre("width", "age")); + ASSERT_THROW(this->EvaluateFunction("KEYS", 2), QueryRuntimeException); } -TEST_F(FunctionTest, Tail) { - ASSERT_THROW(EvaluateFunction("TAIL"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("TAIL", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, Tail) { + ASSERT_THROW(this->EvaluateFunction("TAIL"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("TAIL", TypedValue()).IsNull()); auto argument = MakeTypedValueList(); - ASSERT_EQ(EvaluateFunction("TAIL", argument).ValueList().size(), 0U); + ASSERT_EQ(this->EvaluateFunction("TAIL", argument).ValueList().size(), 0U); argument = MakeTypedValueList(3, 4, true, "john"); - auto list = EvaluateFunction("TAIL", argument).ValueList(); + auto list = this->EvaluateFunction("TAIL", argument).ValueList(); ASSERT_EQ(list.size(), 3U); ASSERT_EQ(list[0].ValueInt(), 4); ASSERT_EQ(list[1].ValueBool(), true); ASSERT_EQ(list[2].ValueString(), "john"); - ASSERT_THROW(EvaluateFunction("TAIL", 2), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("TAIL", 2), QueryRuntimeException); } -TEST_F(FunctionTest, UniformSample) { - ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), TypedValue()).IsNull()); - ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", TypedValue(), 1).IsNull()); - ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), TypedValue()).IsNull()); - ASSERT_TRUE(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), 1).IsNull()); - ASSERT_THROW(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), -1), QueryRuntimeException); - ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 0).ValueList().size(), 0); - ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 2).ValueList().size(), 2); - ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 3).ValueList().size(), 3); - ASSERT_EQ(EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 5).ValueList().size(), 5); +TYPED_TEST(FunctionTest, UniformSample) { + ASSERT_THROW(this->EvaluateFunction("UNIFORMSAMPLE"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("UNIFORMSAMPLE", TypedValue(), TypedValue()).IsNull()); + ASSERT_TRUE(this->EvaluateFunction("UNIFORMSAMPLE", TypedValue(), 1).IsNull()); + ASSERT_TRUE(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), TypedValue()).IsNull()); + ASSERT_TRUE(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(), 1).IsNull()); + ASSERT_THROW(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), -1), QueryRuntimeException); + ASSERT_EQ(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 0).ValueList().size(), 0); + ASSERT_EQ(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 2).ValueList().size(), 2); + ASSERT_EQ(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 3).ValueList().size(), 3); + ASSERT_EQ(this->EvaluateFunction("UNIFORMSAMPLE", MakeTypedValueList(1, 2, 3), 5).ValueList().size(), 5); } -TEST_F(FunctionTest, Abs) { - ASSERT_THROW(EvaluateFunction("ABS"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("ABS", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("ABS", -2).ValueInt(), 2); - ASSERT_EQ(EvaluateFunction("ABS", -2.5).ValueDouble(), 2.5); - ASSERT_THROW(EvaluateFunction("ABS", true), QueryRuntimeException); +TYPED_TEST(FunctionTest, Abs) { + ASSERT_THROW(this->EvaluateFunction("ABS"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("ABS", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("ABS", -2).ValueInt(), 2); + ASSERT_EQ(this->EvaluateFunction("ABS", -2.5).ValueDouble(), 2.5); + ASSERT_THROW(this->EvaluateFunction("ABS", true), QueryRuntimeException); } // Test if log works. If it does then all functions wrapped with // WRAP_CMATH_FLOAT_FUNCTION macro should work and are not gonna be tested for // correctnes.. -TEST_F(FunctionTest, Log) { - ASSERT_THROW(EvaluateFunction("LOG"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("LOG", TypedValue()).IsNull()); - ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 2).ValueDouble(), log(2)); - ASSERT_DOUBLE_EQ(EvaluateFunction("LOG", 1.5).ValueDouble(), log(1.5)); +TYPED_TEST(FunctionTest, Log) { + ASSERT_THROW(this->EvaluateFunction("LOG"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("LOG", TypedValue()).IsNull()); + ASSERT_DOUBLE_EQ(this->EvaluateFunction("LOG", 2).ValueDouble(), log(2)); + ASSERT_DOUBLE_EQ(this->EvaluateFunction("LOG", 1.5).ValueDouble(), log(1.5)); // Not portable, but should work on most platforms. - ASSERT_TRUE(std::isnan(EvaluateFunction("LOG", -1.5).ValueDouble())); - ASSERT_THROW(EvaluateFunction("LOG", true), QueryRuntimeException); + ASSERT_TRUE(std::isnan(this->EvaluateFunction("LOG", -1.5).ValueDouble())); + ASSERT_THROW(this->EvaluateFunction("LOG", true), QueryRuntimeException); } // Function Round wraps round from cmath and will work if FunctionTest.Log test // passes. This test is used to show behavior of round since it differs from // neo4j's round. -TEST_F(FunctionTest, Round) { - ASSERT_THROW(EvaluateFunction("ROUND"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("ROUND", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("ROUND", -2).ValueDouble(), -2); - ASSERT_EQ(EvaluateFunction("ROUND", -2.4).ValueDouble(), -2); - ASSERT_EQ(EvaluateFunction("ROUND", -2.5).ValueDouble(), -3); - ASSERT_EQ(EvaluateFunction("ROUND", -2.6).ValueDouble(), -3); - ASSERT_EQ(EvaluateFunction("ROUND", 2.4).ValueDouble(), 2); - ASSERT_EQ(EvaluateFunction("ROUND", 2.5).ValueDouble(), 3); - ASSERT_EQ(EvaluateFunction("ROUND", 2.6).ValueDouble(), 3); - ASSERT_THROW(EvaluateFunction("ROUND", true), QueryRuntimeException); +TYPED_TEST(FunctionTest, Round) { + ASSERT_THROW(this->EvaluateFunction("ROUND"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("ROUND", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("ROUND", -2).ValueDouble(), -2); + ASSERT_EQ(this->EvaluateFunction("ROUND", -2.4).ValueDouble(), -2); + ASSERT_EQ(this->EvaluateFunction("ROUND", -2.5).ValueDouble(), -3); + ASSERT_EQ(this->EvaluateFunction("ROUND", -2.6).ValueDouble(), -3); + ASSERT_EQ(this->EvaluateFunction("ROUND", 2.4).ValueDouble(), 2); + ASSERT_EQ(this->EvaluateFunction("ROUND", 2.5).ValueDouble(), 3); + ASSERT_EQ(this->EvaluateFunction("ROUND", 2.6).ValueDouble(), 3); + ASSERT_THROW(this->EvaluateFunction("ROUND", true), QueryRuntimeException); } // Check if wrapped functions are callable (check if everything was spelled // correctly...). Wrapper correctnes is checked in FunctionTest.Log function // test. -TEST_F(FunctionTest, WrappedMathFunctions) { +TYPED_TEST(FunctionTest, WrappedMathFunctions) { for (auto function_name : {"FLOOR", "CEIL", "ROUND", "EXP", "LOG", "LOG10", "SQRT", "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN"}) { - EvaluateFunction(function_name, 0.5); + this->EvaluateFunction(function_name, 0.5); } } -TEST_F(FunctionTest, Atan2) { - ASSERT_THROW(EvaluateFunction("ATAN2"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("ATAN2", TypedValue(), 1).IsNull()); - ASSERT_TRUE(EvaluateFunction("ATAN2", 1, TypedValue()).IsNull()); - ASSERT_DOUBLE_EQ(EvaluateFunction("ATAN2", 2, -1.0).ValueDouble(), atan2(2, -1)); - ASSERT_THROW(EvaluateFunction("ATAN2", 3.0, true), QueryRuntimeException); +TYPED_TEST(FunctionTest, Atan2) { + ASSERT_THROW(this->EvaluateFunction("ATAN2"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("ATAN2", TypedValue(), 1).IsNull()); + ASSERT_TRUE(this->EvaluateFunction("ATAN2", 1, TypedValue()).IsNull()); + ASSERT_DOUBLE_EQ(this->EvaluateFunction("ATAN2", 2, -1.0).ValueDouble(), atan2(2, -1)); + ASSERT_THROW(this->EvaluateFunction("ATAN2", 3.0, true), QueryRuntimeException); } -TEST_F(FunctionTest, Sign) { - ASSERT_THROW(EvaluateFunction("SIGN"), QueryRuntimeException); - ASSERT_TRUE(EvaluateFunction("SIGN", TypedValue()).IsNull()); - ASSERT_EQ(EvaluateFunction("SIGN", -2).ValueInt(), -1); - ASSERT_EQ(EvaluateFunction("SIGN", -0.2).ValueInt(), -1); - ASSERT_EQ(EvaluateFunction("SIGN", 0.0).ValueInt(), 0); - ASSERT_EQ(EvaluateFunction("SIGN", 2.5).ValueInt(), 1); - ASSERT_THROW(EvaluateFunction("SIGN", true), QueryRuntimeException); +TYPED_TEST(FunctionTest, Sign) { + ASSERT_THROW(this->EvaluateFunction("SIGN"), QueryRuntimeException); + ASSERT_TRUE(this->EvaluateFunction("SIGN", TypedValue()).IsNull()); + ASSERT_EQ(this->EvaluateFunction("SIGN", -2).ValueInt(), -1); + ASSERT_EQ(this->EvaluateFunction("SIGN", -0.2).ValueInt(), -1); + ASSERT_EQ(this->EvaluateFunction("SIGN", 0.0).ValueInt(), 0); + ASSERT_EQ(this->EvaluateFunction("SIGN", 2.5).ValueInt(), 1); + ASSERT_THROW(this->EvaluateFunction("SIGN", true), QueryRuntimeException); } -TEST_F(FunctionTest, E) { - ASSERT_THROW(EvaluateFunction("E", 1), QueryRuntimeException); - ASSERT_DOUBLE_EQ(EvaluateFunction("E").ValueDouble(), M_E); +TYPED_TEST(FunctionTest, E) { + ASSERT_THROW(this->EvaluateFunction("E", 1), QueryRuntimeException); + ASSERT_DOUBLE_EQ(this->EvaluateFunction("E").ValueDouble(), M_E); } -TEST_F(FunctionTest, Pi) { - ASSERT_THROW(EvaluateFunction("PI", 1), QueryRuntimeException); - ASSERT_DOUBLE_EQ(EvaluateFunction("PI").ValueDouble(), M_PI); +TYPED_TEST(FunctionTest, Pi) { + ASSERT_THROW(this->EvaluateFunction("PI", 1), QueryRuntimeException); + ASSERT_DOUBLE_EQ(this->EvaluateFunction("PI").ValueDouble(), M_PI); } -TEST_F(FunctionTest, Rand) { - ASSERT_THROW(EvaluateFunction("RAND", 1), QueryRuntimeException); - ASSERT_GE(EvaluateFunction("RAND").ValueDouble(), 0.0); - ASSERT_LT(EvaluateFunction("RAND").ValueDouble(), 1.0); +TYPED_TEST(FunctionTest, Rand) { + ASSERT_THROW(this->EvaluateFunction("RAND", 1), QueryRuntimeException); + ASSERT_GE(this->EvaluateFunction("RAND").ValueDouble(), 0.0); + ASSERT_LT(this->EvaluateFunction("RAND").ValueDouble(), 1.0); } -TEST_F(FunctionTest, StartsWith) { - EXPECT_THROW(EvaluateFunction(kStartsWith), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kStartsWith, "a", TypedValue()).IsNull()); - EXPECT_THROW(EvaluateFunction(kStartsWith, TypedValue(), 1.3), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kStartsWith, "abc", "abc").ValueBool()); - EXPECT_TRUE(EvaluateFunction(kStartsWith, "abcdef", "abc").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kStartsWith, "abcdef", "aBc").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kStartsWith, "abc", "abcd").ValueBool()); +TYPED_TEST(FunctionTest, StartsWith) { + EXPECT_THROW(this->EvaluateFunction(kStartsWith), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kStartsWith, "a", TypedValue()).IsNull()); + EXPECT_THROW(this->EvaluateFunction(kStartsWith, TypedValue(), 1.3), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kStartsWith, "abc", "abc").ValueBool()); + EXPECT_TRUE(this->EvaluateFunction(kStartsWith, "abcdef", "abc").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kStartsWith, "abcdef", "aBc").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kStartsWith, "abc", "abcd").ValueBool()); } -TEST_F(FunctionTest, EndsWith) { - EXPECT_THROW(EvaluateFunction(kEndsWith), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kEndsWith, "a", TypedValue()).IsNull()); - EXPECT_THROW(EvaluateFunction(kEndsWith, TypedValue(), 1.3), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kEndsWith, "abc", "abc").ValueBool()); - EXPECT_TRUE(EvaluateFunction(kEndsWith, "abcdef", "def").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kEndsWith, "abcdef", "dEf").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kEndsWith, "bcd", "abcd").ValueBool()); +TYPED_TEST(FunctionTest, EndsWith) { + EXPECT_THROW(this->EvaluateFunction(kEndsWith), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kEndsWith, "a", TypedValue()).IsNull()); + EXPECT_THROW(this->EvaluateFunction(kEndsWith, TypedValue(), 1.3), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kEndsWith, "abc", "abc").ValueBool()); + EXPECT_TRUE(this->EvaluateFunction(kEndsWith, "abcdef", "def").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kEndsWith, "abcdef", "dEf").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kEndsWith, "bcd", "abcd").ValueBool()); } -TEST_F(FunctionTest, Contains) { - EXPECT_THROW(EvaluateFunction(kContains), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kContains, "a", TypedValue()).IsNull()); - EXPECT_THROW(EvaluateFunction(kContains, TypedValue(), 1.3), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction(kContains, "abc", "abc").ValueBool()); - EXPECT_TRUE(EvaluateFunction(kContains, "abcde", "bcd").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kContains, "cde", "abcdef").ValueBool()); - EXPECT_FALSE(EvaluateFunction(kContains, "abcdef", "dEf").ValueBool()); +TYPED_TEST(FunctionTest, Contains) { + EXPECT_THROW(this->EvaluateFunction(kContains), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kContains, "a", TypedValue()).IsNull()); + EXPECT_THROW(this->EvaluateFunction(kContains, TypedValue(), 1.3), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction(kContains, "abc", "abc").ValueBool()); + EXPECT_TRUE(this->EvaluateFunction(kContains, "abcde", "bcd").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kContains, "cde", "abcdef").ValueBool()); + EXPECT_FALSE(this->EvaluateFunction(kContains, "abcdef", "dEf").ValueBool()); } -TEST_F(FunctionTest, Assert) { +TYPED_TEST(FunctionTest, Assert) { // Invalid calls. - ASSERT_THROW(EvaluateFunction("ASSERT"), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("ASSERT", false, false), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("ASSERT", "string", false), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("ASSERT", false, "reason", true), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT"), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT", false, false), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT", "string", false), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT", false, "reason", true), QueryRuntimeException); // Valid calls, assertion fails. - ASSERT_THROW(EvaluateFunction("ASSERT", false), QueryRuntimeException); - ASSERT_THROW(EvaluateFunction("ASSERT", false, "message"), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT", false), QueryRuntimeException); + ASSERT_THROW(this->EvaluateFunction("ASSERT", false, "message"), QueryRuntimeException); try { - EvaluateFunction("ASSERT", false, "bbgba"); + this->EvaluateFunction("ASSERT", false, "bbgba"); } catch (QueryRuntimeException &e) { ASSERT_TRUE(std::string(e.what()).find("bbgba") != std::string::npos); } // Valid calls, assertion passes. - ASSERT_TRUE(EvaluateFunction("ASSERT", true).ValueBool()); - ASSERT_TRUE(EvaluateFunction("ASSERT", true, "message").ValueBool()); + ASSERT_TRUE(this->EvaluateFunction("ASSERT", true).ValueBool()); + ASSERT_TRUE(this->EvaluateFunction("ASSERT", true, "message").ValueBool()); } -TEST_F(FunctionTest, Counter) { - EXPECT_THROW(EvaluateFunction("COUNTER"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("COUNTER", "a"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("COUNTER", "a", "b", "c"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Counter) { + EXPECT_THROW(this->EvaluateFunction("COUNTER"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("COUNTER", "a"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("COUNTER", "a", "b"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("COUNTER", "a", "b", "c"), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 1); - EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 2); - EXPECT_EQ(EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 1); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 1); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c1", 0).ValueInt(), 2); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c2", 0).ValueInt(), 1); - EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), -1); - EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 1); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c3", -1).ValueInt(), -1); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c3", -1).ValueInt(), 1); - EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 5); - EXPECT_EQ(EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 10); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 5); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c4", 0, 5).ValueInt(), 10); - EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -5); - EXPECT_EQ(EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -10); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -5); + EXPECT_EQ(this->EvaluateFunction("COUNTER", "c5", 0, -5).ValueInt(), -10); - EXPECT_THROW(EvaluateFunction("COUNTER", "c6", 0, 0), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("COUNTER", "c6", 0, 0), QueryRuntimeException); } -TEST_F(FunctionTest, Id) { - auto va = dba.InsertVertex(); - auto ea = dba.InsertEdge(&va, &va, dba.NameToEdgeType("edge")); +TYPED_TEST(FunctionTest, Id) { + auto va = this->dba.InsertVertex(); + auto ea = this->dba.InsertEdge(&va, &va, this->dba.NameToEdgeType("edge")); ASSERT_TRUE(ea.HasValue()); - auto vb = dba.InsertVertex(); - dba.AdvanceCommand(); - EXPECT_TRUE(EvaluateFunction("ID", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("ID", va).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("ID", *ea).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("ID", vb).ValueInt(), 1); - EXPECT_THROW(EvaluateFunction("ID"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("ID", 0), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("ID", va, *ea), QueryRuntimeException); + auto vb = this->dba.InsertVertex(); + this->dba.AdvanceCommand(); + EXPECT_TRUE(this->EvaluateFunction("ID", TypedValue()).IsNull()); + EXPECT_EQ(this->EvaluateFunction("ID", va).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("ID", *ea).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("ID", vb).ValueInt(), 1); + EXPECT_THROW(this->EvaluateFunction("ID"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("ID", 0), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("ID", va, *ea), QueryRuntimeException); } -TEST_F(FunctionTest, ToStringNull) { EXPECT_TRUE(EvaluateFunction("TOSTRING", TypedValue()).IsNull()); } +TYPED_TEST(FunctionTest, ToStringNull) { EXPECT_TRUE(this->EvaluateFunction("TOSTRING", TypedValue()).IsNull()); } -TEST_F(FunctionTest, ToStringString) { - EXPECT_EQ(EvaluateFunction("TOSTRING", "").ValueString(), ""); - EXPECT_EQ(EvaluateFunction("TOSTRING", "this is a string").ValueString(), "this is a string"); +TYPED_TEST(FunctionTest, ToStringString) { + EXPECT_EQ(this->EvaluateFunction("TOSTRING", "").ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", "this is a string").ValueString(), "this is a string"); } -TEST_F(FunctionTest, ToStringInteger) { - EXPECT_EQ(EvaluateFunction("TOSTRING", -23321312).ValueString(), "-23321312"); - EXPECT_EQ(EvaluateFunction("TOSTRING", 0).ValueString(), "0"); - EXPECT_EQ(EvaluateFunction("TOSTRING", 42).ValueString(), "42"); +TYPED_TEST(FunctionTest, ToStringInteger) { + EXPECT_EQ(this->EvaluateFunction("TOSTRING", -23321312).ValueString(), "-23321312"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0).ValueString(), "0"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 42).ValueString(), "42"); } -TEST_F(FunctionTest, ToStringDouble) { - EXPECT_EQ(EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000"); - EXPECT_EQ(EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000"); - EXPECT_EQ(EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322"); +TYPED_TEST(FunctionTest, ToStringDouble) { + EXPECT_EQ(this->EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322"); } -TEST_F(FunctionTest, ToStringBool) { - EXPECT_EQ(EvaluateFunction("TOSTRING", true).ValueString(), "true"); - EXPECT_EQ(EvaluateFunction("TOSTRING", false).ValueString(), "false"); +TYPED_TEST(FunctionTest, ToStringBool) { + EXPECT_EQ(this->EvaluateFunction("TOSTRING", true).ValueString(), "true"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", false).ValueString(), "false"); } -TEST_F(FunctionTest, ToStringDate) { +TYPED_TEST(FunctionTest, ToStringDate) { const auto date = memgraph::utils::Date({1970, 1, 2}); - EXPECT_EQ(EvaluateFunction("TOSTRING", date).ValueString(), "1970-01-02"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", date).ValueString(), "1970-01-02"); } -TEST_F(FunctionTest, ToStringLocalTime) { +TYPED_TEST(FunctionTest, ToStringLocalTime) { const auto lt = memgraph::utils::LocalTime({13, 2, 40, 100, 50}); - EXPECT_EQ(EvaluateFunction("TOSTRING", lt).ValueString(), "13:02:40.100050"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", lt).ValueString(), "13:02:40.100050"); } -TEST_F(FunctionTest, ToStringLocalDateTime) { +TYPED_TEST(FunctionTest, ToStringLocalDateTime) { const auto ldt = memgraph::utils::LocalDateTime({1970, 1, 2}, {23, 02, 59}); - EXPECT_EQ(EvaluateFunction("TOSTRING", ldt).ValueString(), "1970-01-02T23:02:59.000000"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", ldt).ValueString(), "1970-01-02T23:02:59.000000"); } -TEST_F(FunctionTest, ToStringDuration) { +TYPED_TEST(FunctionTest, ToStringDuration) { memgraph::utils::Duration duration{{.minute = 2, .second = 2, .microsecond = 33}}; - EXPECT_EQ(EvaluateFunction("TOSTRING", duration).ValueString(), "P0DT0H2M2.000033S"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", duration).ValueString(), "P0DT0H2M2.000033S"); } -TEST_F(FunctionTest, ToStringExceptions) { EXPECT_THROW(EvaluateFunction("TOSTRING", 1, 2, 3), QueryRuntimeException); } - -TEST_F(FunctionTest, TimestampVoid) { - ctx.timestamp = 42; - EXPECT_EQ(EvaluateFunction("TIMESTAMP").ValueInt(), 42); +TYPED_TEST(FunctionTest, ToStringExceptions) { + EXPECT_THROW(this->EvaluateFunction("TOSTRING", 1, 2, 3), QueryRuntimeException); } -TEST_F(FunctionTest, TimestampDate) { - ctx.timestamp = 42; - EXPECT_EQ(EvaluateFunction("TIMESTAMP", memgraph::utils::Date({1970, 1, 1})).ValueInt(), 0); - EXPECT_EQ(EvaluateFunction("TIMESTAMP", memgraph::utils::Date({1971, 1, 1})).ValueInt(), 31536000000000); +TYPED_TEST(FunctionTest, TimestampVoid) { + this->ctx.timestamp = 42; + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP").ValueInt(), 42); } -TEST_F(FunctionTest, TimestampLocalTime) { - ctx.timestamp = 42; +TYPED_TEST(FunctionTest, TimestampDate) { + this->ctx.timestamp = 42; + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP", memgraph::utils::Date({1970, 1, 1})).ValueInt(), 0); + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP", memgraph::utils::Date({1971, 1, 1})).ValueInt(), 31536000000000); +} + +TYPED_TEST(FunctionTest, TimestampLocalTime) { + this->ctx.timestamp = 42; const memgraph::utils::LocalTime time(10000); - EXPECT_EQ(EvaluateFunction("TIMESTAMP", time).ValueInt(), 10000); + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP", time).ValueInt(), 10000); } -TEST_F(FunctionTest, TimestampLocalDateTime) { - ctx.timestamp = 42; +TYPED_TEST(FunctionTest, TimestampLocalDateTime) { + this->ctx.timestamp = 42; const memgraph::utils::LocalDateTime time(20000); - EXPECT_EQ(EvaluateFunction("TIMESTAMP", time).ValueInt(), 20000); + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP", time).ValueInt(), 20000); } -TEST_F(FunctionTest, TimestampDuration) { - ctx.timestamp = 42; +TYPED_TEST(FunctionTest, TimestampDuration) { + this->ctx.timestamp = 42; const memgraph::utils::Duration time(20000); - EXPECT_EQ(EvaluateFunction("TIMESTAMP", time).ValueInt(), 20000); + EXPECT_EQ(this->EvaluateFunction("TIMESTAMP", time).ValueInt(), 20000); } -TEST_F(FunctionTest, TimestampExceptions) { - ctx.timestamp = 42; - EXPECT_THROW(EvaluateFunction("TIMESTAMP", 1).ValueInt(), QueryRuntimeException); +TYPED_TEST(FunctionTest, TimestampExceptions) { + this->ctx.timestamp = 42; + EXPECT_THROW(this->EvaluateFunction("TIMESTAMP", 1).ValueInt(), QueryRuntimeException); } -TEST_F(FunctionTest, Left) { - EXPECT_THROW(EvaluateFunction("LEFT"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Left) { + EXPECT_THROW(this->EvaluateFunction("LEFT"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("LEFT", TypedValue(), 10).IsNull()); - EXPECT_THROW(EvaluateFunction("LEFT", TypedValue(), -10), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("LEFT", TypedValue(), TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("LEFT", TypedValue(), 10).IsNull()); + EXPECT_THROW(this->EvaluateFunction("LEFT", TypedValue(), -10), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 0).ValueString(), ""); - EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 3).ValueString(), "mem"); - EXPECT_EQ(EvaluateFunction("LEFT", "memgraph", 1000).ValueString(), "memgraph"); - EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", -10), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("LEFT", "memgraph", "graph"), QueryRuntimeException); + EXPECT_EQ(this->EvaluateFunction("LEFT", "memgraph", 0).ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("LEFT", "memgraph", 3).ValueString(), "mem"); + EXPECT_EQ(this->EvaluateFunction("LEFT", "memgraph", 1000).ValueString(), "memgraph"); + EXPECT_THROW(this->EvaluateFunction("LEFT", "memgraph", -10), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LEFT", "memgraph", "graph"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("LEFT", 132, 10), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LEFT", 132, 10), QueryRuntimeException); } -TEST_F(FunctionTest, Right) { - EXPECT_THROW(EvaluateFunction("RIGHT"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Right) { + EXPECT_THROW(this->EvaluateFunction("RIGHT"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("RIGHT", TypedValue(), 10).IsNull()); - EXPECT_THROW(EvaluateFunction("RIGHT", TypedValue(), -10), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("RIGHT", TypedValue(), TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("RIGHT", TypedValue(), 10).IsNull()); + EXPECT_THROW(this->EvaluateFunction("RIGHT", TypedValue(), -10), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 0).ValueString(), ""); - EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 3).ValueString(), "aph"); - EXPECT_EQ(EvaluateFunction("RIGHT", "memgraph", 1000).ValueString(), "memgraph"); - EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", -10), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RIGHT", "memgraph", "graph"), QueryRuntimeException); + EXPECT_EQ(this->EvaluateFunction("RIGHT", "memgraph", 0).ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("RIGHT", "memgraph", 3).ValueString(), "aph"); + EXPECT_EQ(this->EvaluateFunction("RIGHT", "memgraph", 1000).ValueString(), "memgraph"); + EXPECT_THROW(this->EvaluateFunction("RIGHT", "memgraph", -10), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RIGHT", "memgraph", "graph"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RIGHT", 132, 10), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RIGHT", 132, 10), QueryRuntimeException); } -TEST_F(FunctionTest, Trimming) { - EXPECT_TRUE(EvaluateFunction("LTRIM", TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("RTRIM", TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("TRIM", TypedValue()).IsNull()); +TYPED_TEST(FunctionTest, Trimming) { + EXPECT_TRUE(this->EvaluateFunction("LTRIM", TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("RTRIM", TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("TRIM", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("LTRIM", " abc ").ValueString(), "abc "); - EXPECT_EQ(EvaluateFunction("RTRIM", " abc ").ValueString(), " abc"); - EXPECT_EQ(EvaluateFunction("TRIM", "abc").ValueString(), "abc"); + EXPECT_EQ(this->EvaluateFunction("LTRIM", " abc ").ValueString(), "abc "); + EXPECT_EQ(this->EvaluateFunction("RTRIM", " abc ").ValueString(), " abc"); + EXPECT_EQ(this->EvaluateFunction("TRIM", "abc").ValueString(), "abc"); - EXPECT_THROW(EvaluateFunction("LTRIM", "x", "y"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("RTRIM", "x", "y"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TRIM", "x", "y"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LTRIM", "x", "y"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("RTRIM", "x", "y"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TRIM", "x", "y"), QueryRuntimeException); } -TEST_F(FunctionTest, Reverse) { - EXPECT_TRUE(EvaluateFunction("REVERSE", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("REVERSE", "abc").ValueString(), "cba"); - EXPECT_THROW(EvaluateFunction("REVERSE", "x", "y"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Reverse) { + EXPECT_TRUE(this->EvaluateFunction("REVERSE", TypedValue()).IsNull()); + EXPECT_EQ(this->EvaluateFunction("REVERSE", "abc").ValueString(), "cba"); + EXPECT_THROW(this->EvaluateFunction("REVERSE", "x", "y"), QueryRuntimeException); } -TEST_F(FunctionTest, Replace) { - EXPECT_THROW(EvaluateFunction("REPLACE"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("REPLACE", TypedValue(), "l", "w").IsNull()); - EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", TypedValue(), "w").IsNull()); - EXPECT_TRUE(EvaluateFunction("REPLACE", "hello", "l", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("REPLACE", "hello", "l", "w").ValueString(), "hewwo"); +TYPED_TEST(FunctionTest, Replace) { + EXPECT_THROW(this->EvaluateFunction("REPLACE"), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("REPLACE", TypedValue(), "l", "w").IsNull()); + EXPECT_TRUE(this->EvaluateFunction("REPLACE", "hello", TypedValue(), "w").IsNull()); + EXPECT_TRUE(this->EvaluateFunction("REPLACE", "hello", "l", TypedValue()).IsNull()); + EXPECT_EQ(this->EvaluateFunction("REPLACE", "hello", "l", "w").ValueString(), "hewwo"); - EXPECT_THROW(EvaluateFunction("REPLACE", 1, "l", "w"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("REPLACE", "hello", 1, "w"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("REPLACE", "hello", "l", 1), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("REPLACE", 1, "l", "w"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("REPLACE", "hello", 1, "w"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("REPLACE", "hello", "l", 1), QueryRuntimeException); } -TEST_F(FunctionTest, Split) { - EXPECT_THROW(EvaluateFunction("SPLIT"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("SPLIT", "one,two", 1), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("SPLIT", 1, "one,two"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Split) { + EXPECT_THROW(this->EvaluateFunction("SPLIT"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("SPLIT", "one,two", 1), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("SPLIT", 1, "one,two"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("SPLIT", "one,two", TypedValue()).IsNull()); - EXPECT_TRUE(EvaluateFunction("SPLIT", TypedValue(), ",").IsNull()); + EXPECT_TRUE(this->EvaluateFunction("SPLIT", TypedValue(), TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("SPLIT", "one,two", TypedValue()).IsNull()); + EXPECT_TRUE(this->EvaluateFunction("SPLIT", TypedValue(), ",").IsNull()); - auto result = EvaluateFunction("SPLIT", "one,two", ","); + auto result = this->EvaluateFunction("SPLIT", "one,two", ","); EXPECT_TRUE(result.IsList()); EXPECT_EQ(result.ValueList()[0].ValueString(), "one"); EXPECT_EQ(result.ValueList()[1].ValueString(), "two"); } -TEST_F(FunctionTest, Substring) { - EXPECT_THROW(EvaluateFunction("SUBSTRING"), QueryRuntimeException); +TYPED_TEST(FunctionTest, Substring) { + EXPECT_THROW(this->EvaluateFunction("SUBSTRING"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("SUBSTRING", TypedValue(), 0, 10).IsNull()); - EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), TypedValue()), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), -10), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, TypedValue()), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("SUBSTRING", TypedValue(), 0, -10), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("SUBSTRING", TypedValue(), 0, 10).IsNull()); + EXPECT_THROW(this->EvaluateFunction("SUBSTRING", TypedValue(), TypedValue()), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("SUBSTRING", TypedValue(), -10), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("SUBSTRING", TypedValue(), 0, TypedValue()), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("SUBSTRING", TypedValue(), 0, -10), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2).ValueString(), "llo"); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 10).ValueString(), ""); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 2, 0).ValueString(), ""); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 3).ValueString(), "ell"); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 4).ValueString(), "ello"); - EXPECT_EQ(EvaluateFunction("SUBSTRING", "hello", 1, 10).ValueString(), "ello"); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 2).ValueString(), "llo"); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 10).ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 2, 0).ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 1, 3).ValueString(), "ell"); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 1, 4).ValueString(), "ello"); + EXPECT_EQ(this->EvaluateFunction("SUBSTRING", "hello", 1, 10).ValueString(), "ello"); } -TEST_F(FunctionTest, ToLower) { - EXPECT_THROW(EvaluateFunction("TOLOWER"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("TOLOWER", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("TOLOWER", "Ab__C").ValueString(), "ab__c"); +TYPED_TEST(FunctionTest, ToLower) { + EXPECT_THROW(this->EvaluateFunction("TOLOWER"), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("TOLOWER", TypedValue()).IsNull()); + EXPECT_EQ(this->EvaluateFunction("TOLOWER", "Ab__C").ValueString(), "ab__c"); } -TEST_F(FunctionTest, ToUpper) { - EXPECT_THROW(EvaluateFunction("TOUPPER"), QueryRuntimeException); - EXPECT_TRUE(EvaluateFunction("TOUPPER", TypedValue()).IsNull()); - EXPECT_EQ(EvaluateFunction("TOUPPER", "Ab__C").ValueString(), "AB__C"); +TYPED_TEST(FunctionTest, ToUpper) { + EXPECT_THROW(this->EvaluateFunction("TOUPPER"), QueryRuntimeException); + EXPECT_TRUE(this->EvaluateFunction("TOUPPER", TypedValue()).IsNull()); + EXPECT_EQ(this->EvaluateFunction("TOUPPER", "Ab__C").ValueString(), "AB__C"); } -TEST_F(FunctionTest, ToByteString) { - EXPECT_THROW(EvaluateFunction("TOBYTESTRING"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", 42), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", TypedValue()), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "", 42), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "ff"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "00"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("TOBYTESTRING", "0xG"), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "").ValueString(), ""); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x").ValueString(), ""); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0X").ValueString(), ""); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x0123456789aAbBcCdDeEfF").ValueString(), +TYPED_TEST(FunctionTest, ToByteString) { + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", 42), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", TypedValue()), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", "", 42), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", "ff"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", "00"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("TOBYTESTRING", "0xG"), QueryRuntimeException); + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "").ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "0x").ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "0X").ValueString(), ""); + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "0x0123456789aAbBcCdDeEfF").ValueString(), "\x01\x23\x45\x67\x89\xAA\xBB\xCC\xDD\xEE\xFF"); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString().size(), 2); - EXPECT_EQ(EvaluateFunction("TOBYTESTRING", "0x042").ValueString(), + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "0x042").ValueString().size(), 2); + EXPECT_EQ(this->EvaluateFunction("TOBYTESTRING", "0x042").ValueString(), memgraph::utils::pmr::string("\x00\x42", 2, memgraph::utils::NewDeleteResource())); } -TEST_F(FunctionTest, FromByteString) { - EXPECT_THROW(EvaluateFunction("FROMBYTESTRING"), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", 42), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("FROMBYTESTRING", TypedValue()), QueryRuntimeException); - EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", "").ValueString(), ""); - auto bytestring = EvaluateFunction("TOBYTESTRING", "0x123456789aAbBcCdDeEfF"); - EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", bytestring).ValueString(), "0x0123456789aabbccddeeff"); - EXPECT_EQ(EvaluateFunction("FROMBYTESTRING", std::string("\x00\x42", 2)).ValueString(), "0x0042"); +TYPED_TEST(FunctionTest, FromByteString) { + EXPECT_THROW(this->EvaluateFunction("FROMBYTESTRING"), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("FROMBYTESTRING", 42), QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("FROMBYTESTRING", TypedValue()), QueryRuntimeException); + EXPECT_EQ(this->EvaluateFunction("FROMBYTESTRING", "").ValueString(), ""); + auto bytestring = this->EvaluateFunction("TOBYTESTRING", "0x123456789aAbBcCdDeEfF"); + EXPECT_EQ(this->EvaluateFunction("FROMBYTESTRING", bytestring).ValueString(), "0x0123456789aabbccddeeff"); + EXPECT_EQ(this->EvaluateFunction("FROMBYTESTRING", std::string("\x00\x42", 2)).ValueString(), "0x0042"); } -TEST_F(FunctionTest, Date) { +TYPED_TEST(FunctionTest, Date) { const auto unix_epoch = memgraph::utils::Date({1970, 1, 1}); - EXPECT_EQ(EvaluateFunction("DATE", "1970-01-01").ValueDate(), unix_epoch); + EXPECT_EQ(this->EvaluateFunction("DATE", "1970-01-01").ValueDate(), unix_epoch); const auto map_param = TypedValue( std::map<std::string, TypedValue>{{"year", TypedValue(1970)}, {"month", TypedValue(1)}, {"day", TypedValue(1)}}); - EXPECT_EQ(EvaluateFunction("DATE", map_param).ValueDate(), unix_epoch); + EXPECT_EQ(this->EvaluateFunction("DATE", map_param).ValueDate(), unix_epoch); const auto today = memgraph::utils::CurrentDate(); - EXPECT_EQ(EvaluateFunction("DATE").ValueDate(), today); + EXPECT_EQ(this->EvaluateFunction("DATE").ValueDate(), today); - EXPECT_THROW(EvaluateFunction("DATE", "{}"), memgraph::utils::BasicException); - EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"years", TypedValue(1970)}}), + EXPECT_THROW(this->EvaluateFunction("DATE", "{}"), memgraph::utils::BasicException); + EXPECT_THROW(this->EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"years", TypedValue(1970)}}), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"mnths", TypedValue(1970)}}), + EXPECT_THROW(this->EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"mnths", TypedValue(1970)}}), QueryRuntimeException); - EXPECT_THROW(EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"dayz", TypedValue(1970)}}), + EXPECT_THROW(this->EvaluateFunction("DATE", std::map<std::string, TypedValue>{{"dayz", TypedValue(1970)}}), QueryRuntimeException); } -TEST_F(FunctionTest, LocalTime) { +TYPED_TEST(FunctionTest, LocalTime) { const auto local_time = memgraph::utils::LocalTime({13, 3, 2, 0, 0}); - EXPECT_EQ(EvaluateFunction("LOCALTIME", "130302").ValueLocalTime(), local_time); + EXPECT_EQ(this->EvaluateFunction("LOCALTIME", "130302").ValueLocalTime(), local_time); const auto one_sec_in_microseconds = 1000000; const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"hour", TypedValue(1)}, {"minute", TypedValue(2)}, {"second", TypedValue(3)}, {"millisecond", TypedValue(4)}, {"microsecond", TypedValue(5)}}); - EXPECT_EQ(EvaluateFunction("LOCALTIME", map_param).ValueLocalTime(), memgraph::utils::LocalTime({1, 2, 3, 4, 5})); + EXPECT_EQ(this->EvaluateFunction("LOCALTIME", map_param).ValueLocalTime(), + memgraph::utils::LocalTime({1, 2, 3, 4, 5})); const auto today = memgraph::utils::CurrentLocalTime(); - EXPECT_NEAR(EvaluateFunction("LOCALTIME").ValueLocalTime().MicrosecondsSinceEpoch(), today.MicrosecondsSinceEpoch(), - one_sec_in_microseconds); + EXPECT_NEAR(this->EvaluateFunction("LOCALTIME").ValueLocalTime().MicrosecondsSinceEpoch(), + today.MicrosecondsSinceEpoch(), one_sec_in_microseconds); - EXPECT_THROW(EvaluateFunction("LOCALTIME", "{}"), memgraph::utils::BasicException); - EXPECT_THROW(EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"hous", TypedValue(1970)}})), - QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LOCALTIME", "{}"), memgraph::utils::BasicException); EXPECT_THROW( - EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"minut", TypedValue(1970)}})), + this->EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"hous", TypedValue(1970)}})), QueryRuntimeException); EXPECT_THROW( - EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), + this->EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"minut", TypedValue(1970)}})), + QueryRuntimeException); + EXPECT_THROW( + this->EvaluateFunction("LOCALTIME", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), QueryRuntimeException); } -TEST_F(FunctionTest, LocalDateTime) { +TYPED_TEST(FunctionTest, LocalDateTime) { const auto local_date_time = memgraph::utils::LocalDateTime({1970, 1, 1}, {13, 3, 2, 0, 0}); - EXPECT_EQ(EvaluateFunction("LOCALDATETIME", "1970-01-01T13:03:02").ValueLocalDateTime(), local_date_time); + EXPECT_EQ(this->EvaluateFunction("LOCALDATETIME", "1970-01-01T13:03:02").ValueLocalDateTime(), local_date_time); const auto today = memgraph::utils::CurrentLocalDateTime(); const auto one_sec_in_microseconds = 1000000; const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"year", TypedValue(1972)}, @@ -2190,20 +2302,20 @@ TEST_F(FunctionTest, LocalDateTime) { {"millisecond", TypedValue(7)}, {"microsecond", TypedValue(8)}}); - EXPECT_EQ(EvaluateFunction("LOCALDATETIME", map_param).ValueLocalDateTime(), + EXPECT_EQ(this->EvaluateFunction("LOCALDATETIME", map_param).ValueLocalDateTime(), memgraph::utils::LocalDateTime({1972, 2, 3}, {4, 5, 6, 7, 8})); - EXPECT_NEAR(EvaluateFunction("LOCALDATETIME").ValueLocalDateTime().MicrosecondsSinceEpoch(), + EXPECT_NEAR(this->EvaluateFunction("LOCALDATETIME").ValueLocalDateTime().MicrosecondsSinceEpoch(), today.MicrosecondsSinceEpoch(), one_sec_in_microseconds); - EXPECT_THROW(EvaluateFunction("LOCALDATETIME", "{}"), memgraph::utils::BasicException); - EXPECT_THROW( - EvaluateFunction("LOCALDATETIME", TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})), - QueryRuntimeException); - EXPECT_THROW( - EvaluateFunction("LOCALDATETIME", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), - QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LOCALDATETIME", "{}"), memgraph::utils::BasicException); + EXPECT_THROW(this->EvaluateFunction("LOCALDATETIME", + TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})), + QueryRuntimeException); + EXPECT_THROW(this->EvaluateFunction("LOCALDATETIME", + TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), + QueryRuntimeException); } -TEST_F(FunctionTest, Duration) { +TYPED_TEST(FunctionTest, Duration) { const auto map_param = TypedValue(std::map<std::string, TypedValue>{{"day", TypedValue(3)}, {"hour", TypedValue(4)}, {"minute", TypedValue(5)}, @@ -2211,12 +2323,14 @@ TEST_F(FunctionTest, Duration) { {"millisecond", TypedValue(7)}, {"microsecond", TypedValue(8)}}); - EXPECT_EQ(EvaluateFunction("DURATION", map_param).ValueDuration(), memgraph::utils::Duration({3, 4, 5, 6, 7, 8})); - EXPECT_THROW(EvaluateFunction("DURATION", "{}"), memgraph::utils::BasicException); - EXPECT_THROW(EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})), - QueryRuntimeException); + EXPECT_EQ(this->EvaluateFunction("DURATION", map_param).ValueDuration(), + memgraph::utils::Duration({3, 4, 5, 6, 7, 8})); + EXPECT_THROW(this->EvaluateFunction("DURATION", "{}"), memgraph::utils::BasicException); EXPECT_THROW( - EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), + this->EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"hours", TypedValue(1970)}})), + QueryRuntimeException); + EXPECT_THROW( + this->EvaluateFunction("DURATION", TypedValue(std::map<std::string, TypedValue>{{"seconds", TypedValue(1970)}})), QueryRuntimeException); const auto map_param_negative = TypedValue(std::map<std::string, TypedValue>{{"day", TypedValue(-3)}, @@ -2225,14 +2339,14 @@ TEST_F(FunctionTest, Duration) { {"second", TypedValue(-6)}, {"millisecond", TypedValue(-7)}, {"microsecond", TypedValue(-8)}}); - EXPECT_EQ(EvaluateFunction("DURATION", map_param_negative).ValueDuration(), + EXPECT_EQ(this->EvaluateFunction("DURATION", map_param_negative).ValueDuration(), memgraph::utils::Duration({-3, -4, -5, -6, -7, -8})); - EXPECT_EQ(EvaluateFunction("DURATION", "P4DT4H5M6.2S").ValueDuration(), + EXPECT_EQ(this->EvaluateFunction("DURATION", "P4DT4H5M6.2S").ValueDuration(), memgraph::utils::Duration({4, 4, 5, 6, 0, 200000})); - EXPECT_EQ(EvaluateFunction("DURATION", "P3DT4H5M6.100S").ValueDuration(), + EXPECT_EQ(this->EvaluateFunction("DURATION", "P3DT4H5M6.100S").ValueDuration(), memgraph::utils::Duration({3, 4, 5, 6, 0, 100000})); - EXPECT_EQ(EvaluateFunction("DURATION", "P3DT4H5M6.100110S").ValueDuration(), + EXPECT_EQ(this->EvaluateFunction("DURATION", "P3DT4H5M6.100110S").ValueDuration(), memgraph::utils::Duration({3, 4, 5, 6, 100, 110})); } } // namespace diff --git a/tests/unit/query_plan.cpp b/tests/unit/query_plan.cpp index 5d2fe6d46..8b14c3bf2 100644 --- a/tests/unit/query_plan.cpp +++ b/tests/unit/query_plan.cpp @@ -83,7 +83,10 @@ auto CheckPlan(memgraph::query::CypherQuery *query, AstStorage &storage, TChecke } template <class T> -class TestPlanner : public ::testing::Test {}; +class TestPlanner : public ::testing::Test { + public: + AstStorage storage; +}; using PlannerTypes = ::testing::Types<Planner>; @@ -96,78 +99,66 @@ TYPED_TEST_CASE(TestPlanner, PlannerTypes); TYPED_TEST(TestPlanner, MatchNodeReturn) { // Test MATCH (n) RETURN n - AstStorage storage; + FakeDbAccessor dba; auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(as_n))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce()); } TYPED_TEST(TestPlanner, CreateNodeReturn) { // Test CREATE (n) RETURN n AS n - AstStorage storage; + FakeDbAccessor dba; auto ident_n = IDENT("n"); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(ident_n, AS("n")))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, ExpectProduce()); } TYPED_TEST(TestPlanner, CreateExpand) { // Test CREATE (n) -[r :rel1]-> (m) - AstStorage storage; - FakeDbAccessor dba; auto relationship = "relationship"; auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); - CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, CreateMultipleNode) { // Test CREATE (n), (m) - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("m"))))); - CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateNode(), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectCreateNode(), ExpectCreateNode(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, CreateNodeExpandNode) { // Test CREATE (n) -[r :rel]-> (m), (l) - AstStorage storage; - FakeDbAccessor dba; auto relationship = "rel"; auto *query = QUERY(SINGLE_QUERY( CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")), PATTERN(NODE("l"))))); - CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode(), + CheckPlan<TypeParam>(query, this->storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, CreateNamedPattern) { // Test CREATE p = (n) -[r :rel]-> (m) - AstStorage storage; - FakeDbAccessor dba; auto relationship = "rel"; auto *query = QUERY(SINGLE_QUERY(CREATE(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); - CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath(), + CheckPlan<TypeParam>(query, this->storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchCreateExpand) { // Test MATCH (n) CREATE (n) -[r :rel1]-> (m) - AstStorage storage; - FakeDbAccessor dba; auto relationship = "relationship"; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m"))))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectCreateExpand(), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectCreateExpand(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchLabeledNodes) { // Test MATCH (n :label) RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = "label"; auto *as_n = NEXPR("n", IDENT("n")); @@ -175,47 +166,44 @@ TYPED_TEST(TestPlanner, MatchLabeledNodes) { { // Without created label index auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); } { // With created label index dba.SetIndexCount(dba.Label(label), 0); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectProduce()); } } TYPED_TEST(TestPlanner, MatchPathReturn) { // Test MATCH (n) -[r :relationship]- (m) RETURN n - AstStorage storage; FakeDbAccessor dba; auto relationship = "relationship"; auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY( SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), RETURN(as_n))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchNamedPatternReturn) { // Test MATCH p = (n) -[r :relationship]- (m) RETURN p - AstStorage storage; FakeDbAccessor dba; auto relationship = "relationship"; auto *as_p = NEXPR("p", IDENT("p")); auto *query = QUERY(SINGLE_QUERY( MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), RETURN(as_p))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectConstructNamedPath(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchNamedPatternWithPredicateReturn) { // Test MATCH p = (n) -[r :relationship]- (m) WHERE 2 = p RETURN p - AstStorage storage; FakeDbAccessor dba; auto relationship = "relationship"; auto *as_p = NEXPR("p", IDENT("p")); @@ -223,14 +211,14 @@ TYPED_TEST(TestPlanner, MatchNamedPatternWithPredicateReturn) { QUERY(SINGLE_QUERY(MATCH(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::BOTH, {relationship}), NODE("m"))), WHERE(EQ(LITERAL(2), IDENT("p"))), RETURN(as_p))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectConstructNamedPath(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) { // Test OPTIONAL MATCH p = (n) -[r]- (m) RETURN p - AstStorage storage; + FakeDbAccessor dba; auto node_n = NODE("n"); auto edge = EDGE("r"); auto node_m = NODE("m"); @@ -240,8 +228,7 @@ TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) { auto symbol_table = memgraph::query::MakeSymbolTable(query); auto get_symbol = [&symbol_table](const auto *ast_node) { return symbol_table.at(*ast_node->identifier_); }; std::vector<Symbol> optional_symbols{get_symbol(pattern), get_symbol(node_n), get_symbol(edge), get_symbol(node_m)}; - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(), new ExpectConstructNamedPath()}; CheckPlan(planner.plan(), symbol_table, ExpectOptional(optional_symbols, optional), ExpectProduce()); DeleteListContent(&optional); @@ -249,83 +236,76 @@ TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) { TYPED_TEST(TestPlanner, MatchWhereReturn) { // Test MATCH (n) WHERE n.property < 42 RETURN n - AstStorage storage; FakeDbAccessor dba; auto property = dba.Property("property"); auto *as_n = NEXPR("n", IDENT("n")); - auto *query = QUERY( - SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(LESS(PROPERTY_LOOKUP("n", property), LITERAL(42))), RETURN(as_n))); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), + WHERE(LESS(PROPERTY_LOOKUP(dba, "n", property), LITERAL(42))), RETURN(as_n))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchDelete) { // Test MATCH (n) DELETE n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n")))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectDelete(), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectDelete(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchNodeSet) { // Test MATCH (n) SET n.prop = 42, n = n, n :label - AstStorage storage; FakeDbAccessor dba; auto prop = dba.Property("prop"); auto label = "label"; - auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP("n", prop), LITERAL(42)), + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(42)), SET("n", IDENT("n")), SET("n", {label}))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels(), - ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), + ExpectSetLabels(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchRemove) { // Test MATCH (n) REMOVE n.prop REMOVE n :label - AstStorage storage; FakeDbAccessor dba; auto prop = dba.Property("prop"); auto label = "label"; auto *query = - QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP("n", prop)), REMOVE("n", {label}))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels(), + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP(dba, "n", prop)), REMOVE("n", {label}))); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchMultiPattern) { // Test MATCH (n) -[r]- (m), (j) -[e]- (i) RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m")), PATTERN(NODE("j"), EDGE("e"), NODE("i"))), RETURN("n"))); // We expect the expansions after the first to have a uniqueness filter in a // single MATCH clause. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpand(), ExpectScanAll(), ExpectExpand(), + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpand(), ExpectScanAll(), ExpectExpand(), ExpectEdgeUniquenessFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchMultiPatternSameStart) { // Test MATCH (n), (n) -[e]- (m) RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n")), PATTERN(NODE("n"), EDGE("e"), NODE("m"))), RETURN("n"))); // We expect the second pattern to generate only an Expand, since another // ScanAll would be redundant. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpand(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpand(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchMultiPatternSameExpandStart) { // Test MATCH (n) -[r]- (m), (m) -[e]- (l) RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m")), PATTERN(NODE("m"), EDGE("e"), NODE("l"))), RETURN("n"))); // We expect the second pattern to generate only an Expand. Another // ScanAll would be redundant, as it would generate the nodes obtained from // expansion. Additionally, a uniqueness filter is expected. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpand(), ExpectExpand(), ExpectEdgeUniquenessFilter(), - ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpand(), ExpectExpand(), + ExpectEdgeUniquenessFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MultiMatch) { // Test MATCH (n) -[r]- (m) MATCH (j) -[e]- (i) -[f]- (h) RETURN n - AstStorage storage; + FakeDbAccessor dba; auto *node_n = NODE("n"); auto *edge_r = EDGE("r"); auto *node_m = NODE("m"); @@ -337,8 +317,7 @@ TYPED_TEST(TestPlanner, MultiMatch) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge_r, node_m)), MATCH(PATTERN(node_j, edge_e, node_i, edge_f, node_h)), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // Multiple MATCH clauses form a Cartesian product, so the uniqueness should // not cross MATCH boundaries. CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectScanAll(), ExpectExpand(), @@ -347,27 +326,25 @@ TYPED_TEST(TestPlanner, MultiMatch) { TYPED_TEST(TestPlanner, MultiMatchSameStart) { // Test MATCH (n) MATCH (n) -[r]- (m) RETURN n - AstStorage storage; + FakeDbAccessor dba; auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), RETURN(as_n))); // Similar to MatchMultiPatternSameStart, we expect only Expand from second // MATCH clause. auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchWithReturn) { // Test MATCH (old) WITH old AS new RETURN new - AstStorage storage; + FakeDbAccessor dba; auto *as_new = NEXPR("new", IDENT("new")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new")), RETURN(as_new))); // No accumulation since we only do reads. auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectProduce()); } @@ -375,25 +352,22 @@ TYPED_TEST(TestPlanner, MatchWithWhereReturn) { // Test MATCH (old) WITH old AS new WHERE new.prop < 42 RETURN new FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *as_new = NEXPR("new", IDENT("new")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new")), - WHERE(LESS(PROPERTY_LOOKUP("new", prop), LITERAL(42))), RETURN(as_new))); + WHERE(LESS(PROPERTY_LOOKUP(dba, "new", prop), LITERAL(42))), RETURN(as_new))); // No accumulation since we only do reads. auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, CreateMultiExpand) { // Test CREATE (n) -[r :r]-> (m), (n) - [p :p]-> (l) - FakeDbAccessor dba; auto r = "r"; auto p = "p"; - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r}), NODE("m")), PATTERN(NODE("n"), EDGE("p", Direction::OUT, {p}), NODE("l"))))); - CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand(), + CheckPlan<TypeParam>(query, this->storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand(), ExpectEmptyResult()); } @@ -402,13 +376,12 @@ TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) { // RETURN sum AS result FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; - auto sum = SUM(PROPERTY_LOOKUP("n", prop), false); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop), false); auto literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(ADD(sum, literal), AS("sum")), WHERE(LESS(IDENT("sum"), LITERAL(42))), RETURN("sum", AS("result")))); auto aggr = ExpectAggregate({sum}, {literal}); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchReturnSum) { @@ -416,13 +389,12 @@ TYPED_TEST(TestPlanner, MatchReturnSum) { FakeDbAccessor dba; auto prop1 = dba.Property("prop1"); auto prop2 = dba.Property("prop2"); - AstStorage storage; - auto sum = SUM(PROPERTY_LOOKUP("n", prop1), false); - auto n_prop2 = PROPERTY_LOOKUP("n", prop2); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop1), false); + auto n_prop2 = PROPERTY_LOOKUP(dba, "n", prop2); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(sum, AS("sum"), n_prop2, AS("group")))); auto aggr = ExpectAggregate({sum}, {n_prop2}); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); } @@ -430,15 +402,14 @@ TYPED_TEST(TestPlanner, CreateWithSum) { // Test CREATE (n) WITH SUM(n.prop) AS sum FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto ident_n = IDENT("n"); - auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto n_prop = PROPERTY_LOOKUP(dba, ident_n, prop); auto sum = SUM(n_prop, false); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(sum, AS("sum")))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); auto aggr = ExpectAggregate({sum}, {}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect both the accumulation and aggregation because the part before // WITH updates the database. CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce()); @@ -449,13 +420,12 @@ TYPED_TEST(TestPlanner, MatchWithSumWithDistinctWhereReturn) { // RETURN sum AS result FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; - auto sum = SUM(PROPERTY_LOOKUP("n", prop), true); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop), true); auto literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(ADD(sum, literal), AS("sum")), WHERE(LESS(IDENT("sum"), LITERAL(42))), RETURN("sum", AS("result")))); auto aggr = ExpectAggregate({sum}, {literal}); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchReturnSumWithDistinct) { @@ -463,13 +433,12 @@ TYPED_TEST(TestPlanner, MatchReturnSumWithDistinct) { FakeDbAccessor dba; auto prop1 = dba.Property("prop1"); auto prop2 = dba.Property("prop2"); - AstStorage storage; - auto sum = SUM(PROPERTY_LOOKUP("n", prop1), true); - auto n_prop2 = PROPERTY_LOOKUP("n", prop2); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop1), true); + auto n_prop2 = PROPERTY_LOOKUP(dba, "n", prop2); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(sum, AS("sum"), n_prop2, AS("group")))); auto aggr = ExpectAggregate({sum}, {n_prop2}); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce()); } @@ -477,15 +446,14 @@ TYPED_TEST(TestPlanner, CreateWithSumWithDistinct) { // Test CREATE (n) WITH SUM(DISTINCT n.prop) AS sum FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto ident_n = IDENT("n"); - auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto n_prop = PROPERTY_LOOKUP(dba, ident_n, prop); auto sum = SUM(n_prop, true); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(sum, AS("sum")))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); auto aggr = ExpectAggregate({sum}, {}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect both the accumulation and aggregation because the part before // WITH updates the database. CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce()); @@ -493,35 +461,32 @@ TYPED_TEST(TestPlanner, CreateWithSumWithDistinct) { TYPED_TEST(TestPlanner, MatchWithCreate) { // Test MATCH (n) WITH n AS a CREATE (a) -[r :r]-> (b) - FakeDbAccessor dba; auto r_type = "r"; - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")), CREATE(PATTERN(NODE("a"), EDGE("r", Direction::OUT, {r_type}), NODE("b"))))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand(), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand(), + ExpectEmptyResult()); } TYPED_TEST(TestPlanner, MatchReturnSkipLimit) { // Test MATCH (n) RETURN n SKIP 2 LIMIT 1 - AstStorage storage; + FakeDbAccessor dba; auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(as_n, SKIP(LITERAL(2)), LIMIT(LITERAL(1))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectSkip(), ExpectLimit()); } TYPED_TEST(TestPlanner, CreateWithSkipReturnLimit) { // Test CREATE (n) WITH n AS m SKIP 2 RETURN m LIMIT 1 - AstStorage storage; + FakeDbAccessor dba; auto ident_n = IDENT("n"); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(ident_n, AS("m"), SKIP(LITERAL(2))), RETURN("m", LIMIT(LITERAL(1))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // Since we have a write query, we need to have Accumulate. This is a bit // different than Neo4j 3.0, which optimizes WITH followed by RETURN as a // single RETURN clause and then moves Skip and Limit before Accumulate. @@ -535,16 +500,15 @@ TYPED_TEST(TestPlanner, CreateReturnSumSkipLimit) { // Test CREATE (n) RETURN SUM(n.prop) AS s SKIP 2 LIMIT 1 FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto ident_n = IDENT("n"); - auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto n_prop = PROPERTY_LOOKUP(dba, ident_n, prop); auto sum = SUM(n_prop, false); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(sum, AS("s"), SKIP(LITERAL(2)), LIMIT(LITERAL(1))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); auto aggr = ExpectAggregate({sum}, {}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectSkip(), ExpectLimit()); } @@ -552,16 +516,15 @@ TYPED_TEST(TestPlanner, CreateReturnSumWithDistinctSkipLimit) { // Test CREATE (n) RETURN SUM(n.prop) AS s SKIP 2 LIMIT 1 FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto ident_n = IDENT("n"); - auto n_prop = PROPERTY_LOOKUP(ident_n, prop); + auto n_prop = PROPERTY_LOOKUP(dba, ident_n, prop); auto sum = SUM(n_prop, true); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(sum, AS("s"), SKIP(LITERAL(2)), LIMIT(LITERAL(1))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); auto aggr = ExpectAggregate({sum}, {}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectSkip(), ExpectLimit()); } @@ -569,13 +532,12 @@ TYPED_TEST(TestPlanner, MatchReturnOrderBy) { // Test MATCH (n) RETURN n AS m ORDER BY n.prop FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *as_m = NEXPR("m", IDENT("n")); auto *node_n = NODE("n"); - auto ret = RETURN(as_m, ORDER_BY(PROPERTY_LOOKUP("n", prop))); + auto ret = RETURN(as_m, ORDER_BY(PROPERTY_LOOKUP(dba, "n", prop))); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), ret)); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectProduce(), ExpectOrderBy()); } @@ -585,13 +547,12 @@ TYPED_TEST(TestPlanner, CreateWithOrderByWhere) { FakeDbAccessor dba; auto prop = dba.Property("prop"); auto r_type = "r"; - AstStorage storage; auto ident_n = IDENT("n"); auto ident_r = IDENT("r"); auto ident_m = IDENT("m"); - auto new_prop = PROPERTY_LOOKUP("new", prop); - auto r_prop = PROPERTY_LOOKUP(ident_r, prop); - auto m_prop = PROPERTY_LOOKUP(ident_m, prop); + auto new_prop = PROPERTY_LOOKUP(dba, "new", prop); + auto r_prop = PROPERTY_LOOKUP(dba, ident_r, prop); + auto m_prop = PROPERTY_LOOKUP(dba, ident_m, prop); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r_type}), NODE("m"))), WITH(ident_n, AS("new"), ORDER_BY(new_prop, r_prop)), WHERE(LESS(m_prop, LITERAL(42))))); @@ -602,29 +563,27 @@ TYPED_TEST(TestPlanner, CreateWithOrderByWhere) { symbol_table.at(*ident_r), // `r` in ORDER BY symbol_table.at(*ident_m), // `m` in WHERE }); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(), ExpectOrderBy(), ExpectFilter(), ExpectEmptyResult()); } TYPED_TEST(TestPlanner, ReturnAddSumCountOrderBy) { // Test RETURN SUM(1) + COUNT(2) AS result ORDER BY result - AstStorage storage; auto sum = SUM(LITERAL(1), false); auto count = COUNT(LITERAL(2), false); auto *query = QUERY(SINGLE_QUERY(RETURN(ADD(sum, count), AS("result"), ORDER_BY(IDENT("result"))))); auto aggr = ExpectAggregate({sum, count}, {}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce(), ExpectOrderBy()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce(), ExpectOrderBy()); } TYPED_TEST(TestPlanner, ReturnAddSumCountWithDistinctOrderBy) { // Test RETURN SUM(1) + COUNT(2) AS result ORDER BY result - AstStorage storage; auto sum = SUM(LITERAL(1), true); auto count = COUNT(LITERAL(2), true); auto *query = QUERY(SINGLE_QUERY(RETURN(ADD(sum, count), AS("result"), ORDER_BY(IDENT("result"))))); auto aggr = ExpectAggregate({sum, count}, {}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce(), ExpectOrderBy()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce(), ExpectOrderBy()); } TYPED_TEST(TestPlanner, MatchMerge) { @@ -634,19 +593,18 @@ TYPED_TEST(TestPlanner, MatchMerge) { FakeDbAccessor dba; auto r_type = "r"; auto prop = dba.Property("prop"); - AstStorage storage; auto ident_n = IDENT("n"); - auto query = - QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), - MERGE(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {r_type}), NODE("m")), - ON_MATCH(SET(PROPERTY_LOOKUP("n", prop), LITERAL(42))), ON_CREATE(SET("m", IDENT("n")))), - RETURN(ident_n, AS("n")))); + auto query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), + MERGE(PATTERN(NODE("n"), EDGE("r", Direction::BOTH, {r_type}), NODE("m")), + ON_MATCH(SET(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(42))), ON_CREATE(SET("m", IDENT("n")))), + RETURN(ident_n, AS("n")))); std::list<BaseOpChecker *> on_match{new ExpectExpand(), new ExpectSetProperty()}; std::list<BaseOpChecker *> on_create{new ExpectCreateExpand(), new ExpectSetProperties()}; auto symbol_table = memgraph::query::MakeSymbolTable(query); // We expect Accumulate after Merge, because it is considered as a write. auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectMerge(on_match, on_create), acc, ExpectProduce()); DeleteListContent(&on_match); DeleteListContent(&on_create); @@ -656,32 +614,29 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchWhereReturn) { // Test MATCH (n) OPTIONAL MATCH (n) -[r]- (m) WHERE m.prop < 42 RETURN r FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), OPTIONAL_MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), - WHERE(LESS(PROPERTY_LOOKUP("m", prop), LITERAL(42))), RETURN("r"))); + WHERE(LESS(PROPERTY_LOOKUP(dba, "m", prop), LITERAL(42))), RETURN("r"))); std::list<BaseOpChecker *> optional{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter()}; - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectOptional(optional), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectOptional(optional), ExpectProduce()); DeleteListContent(&optional); } TYPED_TEST(TestPlanner, MatchOptionalMatchNodePropertyWithIndex) { // Test MATCH (n:Label) OPTIONAL MATCH (m:Label) WHERE n.prop = m.prop RETURN n - AstStorage storage; FakeDbAccessor dba; - const auto label_name = "label"; const auto label = dba.Label(label_name); - const auto property = PROPERTY_PAIR("prop"); + const auto property = PROPERTY_PAIR(dba, "prop"); dba.SetIndexCount(label, property.second, 0); auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n", label_name))), OPTIONAL_MATCH(PATTERN(NODE("m", label_name))), - WHERE(EQ(PROPERTY_LOOKUP("n", property.second), PROPERTY_LOOKUP("m", property.second))), RETURN("n"))); + WHERE(EQ(PROPERTY_LOOKUP(dba, "n", property.second), PROPERTY_LOOKUP(dba, "m", property.second))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); - auto m_prop = PROPERTY_LOOKUP("m", property); + auto m_prop = PROPERTY_LOOKUP(dba, "m", property); std::list<BaseOpChecker *> optional{new ExpectScanAllByLabelPropertyValue(label, property, m_prop)}; CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectOptional(optional), ExpectProduce()); @@ -690,38 +645,36 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchNodePropertyWithIndex) { TYPED_TEST(TestPlanner, MatchUnwindReturn) { // Test MATCH (n) UNWIND [1,2,3] AS x RETURN n, x - AstStorage storage; + FakeDbAccessor dba; auto *as_n = NEXPR("n", IDENT("n")); auto *as_x = NEXPR("x", IDENT("x")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), UNWIND(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), AS("x")), RETURN(as_n, as_x))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectUnwind(), ExpectProduce()); } TYPED_TEST(TestPlanner, ReturnDistinctOrderBySkipLimit) { // Test RETURN DISTINCT 1 ORDER BY 1 SKIP 1 LIMIT 1 - AstStorage storage; auto *query = QUERY( SINGLE_QUERY(RETURN_DISTINCT(LITERAL(1), AS("1"), ORDER_BY(LITERAL(1)), SKIP(LITERAL(1)), LIMIT(LITERAL(1))))); - CheckPlan<TypeParam>(query, storage, ExpectProduce(), ExpectDistinct(), ExpectOrderBy(), ExpectSkip(), ExpectLimit()); + CheckPlan<TypeParam>(query, this->storage, ExpectProduce(), ExpectDistinct(), ExpectOrderBy(), ExpectSkip(), + ExpectLimit()); } TYPED_TEST(TestPlanner, CreateWithDistinctSumWhereReturn) { // Test CREATE (n) WITH DISTINCT SUM(n.prop) AS s WHERE s < 42 RETURN s FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto node_n = NODE("n"); - auto sum = SUM(PROPERTY_LOOKUP("n", prop), false); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop), false); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_n)), WITH_DISTINCT(sum, AS("s")), WHERE(LESS(IDENT("s"), LITERAL(42))), RETURN("s"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto acc = ExpectAccumulate({symbol_table.at(*node_n->identifier_)}); auto aggr = ExpectAggregate({sum}, {}); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectDistinct(), ExpectFilter(), ExpectProduce()); } @@ -729,58 +682,56 @@ TYPED_TEST(TestPlanner, CreateWithDistinctSumWhereReturn) { TYPED_TEST(TestPlanner, MatchCrossReferenceVariable) { // Test MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n FakeDbAccessor dba; - auto prop = PROPERTY_PAIR("prop"); - AstStorage storage; + auto prop = PROPERTY_PAIR(dba, "prop"); auto node_n = NODE("n"); - auto m_prop = PROPERTY_LOOKUP("m", prop.second); - std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = m_prop; + auto m_prop = PROPERTY_LOOKUP(dba, "m", prop.second); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(prop.first)] = m_prop; auto node_m = NODE("m"); - auto n_prop = PROPERTY_LOOKUP("n", prop.second); - std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto n_prop = PROPERTY_LOOKUP(dba, "n", prop.second); + std::get<0>(node_m->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN("n"))); // We expect both ScanAll to come before filters (2 are joined into one), // because they need to populate the symbol values. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectScanAll(), ExpectFilter(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectScanAll(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchWhereBeforeExpand) { // Test MATCH (n) -[r]- (m) WHERE n.prop < 42 RETURN n FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *as_n = NEXPR("n", IDENT("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), - WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN(as_n))); + WHERE(LESS(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(42))), RETURN(as_n))); // We expect Filter to come immediately after ScanAll, since it only uses `n`. auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { FakeDbAccessor dba; auto label = dba.Label("label"); - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(dba, "prop"); dba.SetIndexCount(label, 1); dba.SetIndexCount(label, prop.second, 1); - AstStorage storage; { // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL RETURN n auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), - WHERE(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop)))), RETURN("n"))); + WHERE(NOT(IS_NULL(PROPERTY_LOOKUP(dba, "n", prop)))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect ScanAllByLabelProperty to come instead of ScanAll > Filter. CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectExpand(), ExpectProduce()); } { // Test MATCH (n :label) -[r]- (m) WHERE n.prop IS NOT NULL OR true RETURN n - auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), - WHERE(OR(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop))), LITERAL(true))), RETURN("n"))); + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + WHERE(OR(NOT(IS_NULL(PROPERTY_LOOKUP(dba, "n", prop))), LITERAL(true))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect ScanAllBy > Filter because of the "or true" condition. CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectProduce()); } @@ -788,13 +739,13 @@ TYPED_TEST(TestPlanner, MatchFilterPropIsNotNull) { { // Test MATCH (n :label) -[r]- (m) // WHERE n.prop IS NOT NULL AND n.x = 2 RETURN n - auto prop_x = PROPERTY_PAIR("x"); - auto *query = QUERY( - SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), - WHERE(AND(NOT(IS_NULL(PROPERTY_LOOKUP("n", prop))), EQ(PROPERTY_LOOKUP("n", prop_x), LITERAL(2)))), - RETURN("n"))); + auto prop_x = PROPERTY_PAIR(dba, "x"); + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n", "label"), EDGE("r"), NODE("m"))), + WHERE(AND(NOT(IS_NULL(PROPERTY_LOOKUP(dba, "n", prop))), EQ(PROPERTY_LOOKUP(dba, "n", prop_x), LITERAL(2)))), + RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect ScanAllByLabelProperty > Filter // to come instead of ScanAll > Filter. CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelProperty(label, prop), ExpectFilter(), ExpectExpand(), @@ -806,12 +757,11 @@ TYPED_TEST(TestPlanner, MultiMatchWhere) { // Test MATCH (n) -[r]- (m) MATCH (l) WHERE n.prop < 42 RETURN n FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), MATCH(PATTERN(NODE("l"))), - WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN("n"))); + WHERE(LESS(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(42))), RETURN("n"))); // Even though WHERE is in the second MATCH clause, we expect Filter to come // before second ScanAll, since it only uses the value from first ScanAll. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectScanAll(), + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectScanAll(), ExpectProduce()); } @@ -819,14 +769,14 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchWhere) { // Test MATCH (n) -[r]- (m) OPTIONAL MATCH (l) WHERE n.prop < 42 RETURN n FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), OPTIONAL_MATCH(PATTERN(NODE("l"))), - WHERE(LESS(PROPERTY_LOOKUP("n", prop), LITERAL(42))), RETURN("n"))); + WHERE(LESS(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(42))), RETURN("n"))); // Even though WHERE is in the second MATCH clause, and it uses the value from // first ScanAll, it must remain part of the Optional. It should come before // optional ScanAll. std::list<BaseOpChecker *> optional{new ExpectFilter(), new ExpectScanAll()}; - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpand(), ExpectOptional(optional), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpand(), ExpectOptional(optional), + ExpectProduce()); DeleteListContent(&optional); } @@ -834,12 +784,11 @@ TYPED_TEST(TestPlanner, MatchReturnAsterisk) { // Test MATCH (n) -[e]- (m) RETURN *, m.prop FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; - auto ret = RETURN(PROPERTY_LOOKUP("m", prop), AS("m.prop")); + auto ret = RETURN(PROPERTY_LOOKUP(dba, "m", prop), AS("m.prop")); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))), ret)); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectProduce()); std::vector<std::string> output_names; for (const auto &output_symbol : planner.plan().OutputSymbols(symbol_table)) { @@ -853,13 +802,12 @@ TYPED_TEST(TestPlanner, MatchReturnAsteriskSum) { // Test MATCH (n) RETURN *, SUM(n.prop) AS s FakeDbAccessor dba; auto prop = dba.Property("prop"); - AstStorage storage; - auto sum = SUM(PROPERTY_LOOKUP("n", prop), false); + auto sum = SUM(PROPERTY_LOOKUP(dba, "n", prop), false); auto ret = RETURN(sum, AS("s")); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ret)); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); auto *produce = dynamic_cast<Produce *>(&planner.plan()); ASSERT_TRUE(produce); const auto &named_expressions = produce->named_expressions_; @@ -878,33 +826,30 @@ TYPED_TEST(TestPlanner, MatchReturnAsteriskSum) { TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) { // Test UNWIND [1] AS i MERGE (n {prop: i}) - AstStorage storage; - FakeDbAccessor dba; auto node_n = NODE("n"); - std::get<0>(node_n->properties_)[storage.GetPropertyIx("prop")] = IDENT("i"); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx("prop")] = IDENT("i"); auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n)))); std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()}; std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; - CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult()); DeleteListContent(&on_match); DeleteListContent(&on_create); } TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) { // Test UNWIND [1] AS i MERGE (n :label {prop: i}) with label-property index - AstStorage storage; FakeDbAccessor dba; const auto label_name = "label"; const auto label = dba.Label(label_name); - const auto property = PROPERTY_PAIR("prop"); + const auto property = PROPERTY_PAIR(dba, "prop"); dba.SetIndexCount(label, property.second, 1); auto node_n = NODE("n", label_name); - std::get<0>(node_n->properties_)[storage.GetPropertyIx(property.first)] = IDENT("i"); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(property.first)] = IDENT("i"); auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n)))); std::list<BaseOpChecker *> on_match{new ExpectScanAllByLabelPropertyValue(label, property, IDENT("i"))}; std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult()); DeleteListContent(&on_match); DeleteListContent(&on_create); @@ -912,51 +857,45 @@ TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) { TYPED_TEST(TestPlanner, MultipleOptionalMatchReturn) { // Test OPTIONAL MATCH (n) OPTIONAL MATCH (m) RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(OPTIONAL_MATCH(PATTERN(NODE("n"))), OPTIONAL_MATCH(PATTERN(NODE("m"))), RETURN("n"))); std::list<BaseOpChecker *> optional{new ExpectScanAll()}; - CheckPlan<TypeParam>(query, storage, ExpectOptional(optional), ExpectOptional(optional), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectOptional(optional), ExpectOptional(optional), ExpectProduce()); DeleteListContent(&optional); } TYPED_TEST(TestPlanner, FunctionAggregationReturn) { // Test RETURN sqrt(SUM(2)) AS result, 42 AS group_by - AstStorage storage; auto sum = SUM(LITERAL(2), false); auto group_by_literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(RETURN(FN("sqrt", sum), AS("result"), group_by_literal, AS("group_by")))); auto aggr = ExpectAggregate({sum}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, FunctionWithoutArguments) { // Test RETURN pi() AS pi - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(RETURN(FN("pi"), AS("pi")))); - CheckPlan<TypeParam>(query, storage, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectProduce()); } TYPED_TEST(TestPlanner, ListLiteralAggregationReturn) { // Test RETURN [SUM(2)] AS result, 42 AS group_by - AstStorage storage; auto sum = SUM(LITERAL(2), false); auto group_by_literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum), AS("result"), group_by_literal, AS("group_by")))); auto aggr = ExpectAggregate({sum}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, MapLiteralAggregationReturn) { // Test RETURN {sum: SUM(2)} AS result, 42 AS group_by - AstStorage storage; - FakeDbAccessor dba; auto sum = SUM(LITERAL(2), false); auto group_by_literal = LITERAL(42); - auto *query = QUERY( - SINGLE_QUERY(RETURN(MAP({storage.GetPropertyIx("sum"), sum}), AS("result"), group_by_literal, AS("group_by")))); + auto *query = QUERY(SINGLE_QUERY( + RETURN(MAP({this->storage.GetPropertyIx("sum"), sum}), AS("result"), group_by_literal, AS("group_by")))); auto aggr = ExpectAggregate({sum}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, MapProjectionLiteralAggregationReturn) { @@ -975,22 +914,21 @@ TYPED_TEST(TestPlanner, MapProjectionLiteralAggregationReturn) { TYPED_TEST(TestPlanner, EmptyListIndexAggregation) { // Test RETURN [][SUM(2)] AS result, 42 AS group_by - AstStorage storage; auto sum = SUM(LITERAL(2), false); auto empty_list = LIST(); auto group_by_literal = LITERAL(42); - auto *query = QUERY(SINGLE_QUERY(RETURN(storage.Create<memgraph::query::SubscriptOperator>(empty_list, sum), - AS("result"), group_by_literal, AS("group_by")))); + auto *query = + QUERY(SINGLE_QUERY(RETURN(this->storage.template Create<memgraph::query::SubscriptOperator>(empty_list, sum), + AS("result"), group_by_literal, AS("group_by")))); // We expect to group by '42' and the empty list, because it is a // sub-expression of a binary operator which contains an aggregation. This is // similar to grouping by '1' in `RETURN 1 + SUM(2)`. auto aggr = ExpectAggregate({sum}, {empty_list, group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, ListSliceAggregationReturn) { // Test RETURN [1, 2][0..SUM(2)] AS result, 42 AS group_by - AstStorage storage; auto sum = SUM(LITERAL(2), false); auto list = LIST(LITERAL(1), LITERAL(2)); auto group_by_literal = LITERAL(42); @@ -999,40 +937,37 @@ TYPED_TEST(TestPlanner, ListSliceAggregationReturn) { // Similarly to EmptyListIndexAggregation test, we expect grouping by list and // '42', because slicing is an operator. auto aggr = ExpectAggregate({sum}, {list, group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, ListWithAggregationAndGroupBy) { // Test RETURN [sum(2), 42] - AstStorage storage; auto sum = SUM(LITERAL(2), false); auto group_by_literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum, group_by_literal), AS("result")))); auto aggr = ExpectAggregate({sum}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, AggregatonWithListWithAggregationAndGroupBy) { // Test RETURN sum(2), [sum(3), 42] - AstStorage storage; auto sum2 = SUM(LITERAL(2), false); auto sum3 = SUM(LITERAL(3), false); auto group_by_literal = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(RETURN(sum2, AS("sum2"), LIST(sum3, group_by_literal), AS("list")))); auto aggr = ExpectAggregate({sum2, sum3}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, MapWithAggregationAndGroupBy) { - // Test RETURN {lit: 42, sum: sum(2)} AS result - AstStorage storage; - FakeDbAccessor dba; + // Test RETURN {lit: 42, sum: sum(2)} auto sum = SUM(LITERAL(2), false); auto group_by_literal = LITERAL(42); - auto *query = QUERY(SINGLE_QUERY(RETURN( - MAP({storage.GetPropertyIx("sum"), sum}, {storage.GetPropertyIx("lit"), group_by_literal}), AS("result")))); + auto *query = QUERY(SINGLE_QUERY( + RETURN(MAP({this->storage.GetPropertyIx("sum"), sum}, {this->storage.GetPropertyIx("lit"), group_by_literal}), + AS("result")))); auto aggr = ExpectAggregate({sum}, {group_by_literal}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, MapProjectionWithAggregationAndGroupBy) { @@ -1051,80 +986,76 @@ TYPED_TEST(TestPlanner, MapProjectionWithAggregationAndGroupBy) { TYPED_TEST(TestPlanner, AtomIndexedLabelProperty) { // Test MATCH (n :label {property: 42, not_indexed: 0}) RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); - auto not_indexed = PROPERTY_PAIR("not_indexed"); + auto property = PROPERTY_PAIR(dba, "property"); + auto not_indexed = PROPERTY_PAIR(dba, "not_indexed"); dba.SetIndexCount(label, 1); dba.SetIndexCount(label, property.second, 1); auto node = NODE("n", "label"); auto lit_42 = LITERAL(42); - std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42; - std::get<0>(node->properties_)[storage.GetPropertyIx(not_indexed.first)] = LITERAL(0); + std::get<0>(node->properties_)[this->storage.GetPropertyIx(property.first)] = lit_42; + std::get<0>(node->properties_)[this->storage.GetPropertyIx(not_indexed.first)] = LITERAL(0); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, AtomPropertyWhereLabelIndexing) { // Test MATCH (n {property: 42}) WHERE n.not_indexed AND n:label RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); - auto not_indexed = PROPERTY_PAIR("not_indexed"); + auto property = PROPERTY_PAIR(dba, "property"); + auto not_indexed = PROPERTY_PAIR(dba, "not_indexed"); dba.SetIndexCount(label, property.second, 0); auto node = NODE("n"); auto lit_42 = LITERAL(42); - std::get<0>(node->properties_)[storage.GetPropertyIx(property.first)] = lit_42; - auto *query = QUERY( - SINGLE_QUERY(MATCH(PATTERN(node)), - WHERE(AND(PROPERTY_LOOKUP("n", not_indexed), - storage.Create<memgraph::query::LabelsTest>( - IDENT("n"), std::vector<memgraph::query::LabelIx>{storage.GetLabelIx("label")}))), - RETURN("n"))); + std::get<0>(node->properties_)[this->storage.GetPropertyIx(property.first)] = lit_42; + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(node)), + WHERE(AND(PROPERTY_LOOKUP(dba, "n", not_indexed), + this->storage.template Create<memgraph::query::LabelsTest>( + IDENT("n"), std::vector<memgraph::query::LabelIx>{this->storage.GetLabelIx("label")}))), + RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, WhereIndexedLabelProperty) { // Test MATCH (n :label) WHERE n.property = 42 RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); dba.SetIndexCount(label, property.second, 0); auto lit_42 = LITERAL(42); - auto *query = QUERY( - SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(EQ(PROPERTY_LOOKUP("n", property), lit_42)), RETURN("n"))); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(EQ(PROPERTY_LOOKUP(dba, "n", property), lit_42)), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectProduce()); } TYPED_TEST(TestPlanner, BestPropertyIndexed) { // Test MATCH (n :label) WHERE n.property = 1 AND n.better = 42 RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); auto property = dba.Property("property"); // Add a vertex with :label+property combination, so that the best // :label+better remains empty and thus better choice. dba.SetIndexCount(label, property, 1); - auto better = PROPERTY_PAIR("better"); + auto better = PROPERTY_PAIR(dba, "better"); dba.SetIndexCount(label, better.second, 0); auto lit_42 = LITERAL(42); - auto *query = QUERY( - SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), LITERAL(1)), EQ(PROPERTY_LOOKUP("n", better), lit_42))), - RETURN("n"))); + auto *query = QUERY(SINGLE_QUERY( + MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(EQ(PROPERTY_LOOKUP(dba, "n", property), LITERAL(1)), EQ(PROPERTY_LOOKUP(dba, "n", better), lit_42))), + RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, better, lit_42), ExpectFilter(), ExpectProduce()); } @@ -1135,18 +1066,18 @@ TYPED_TEST(TestPlanner, MultiPropertyIndexScan) { FakeDbAccessor dba; auto label1 = dba.Label("label1"); auto label2 = dba.Label("label2"); - auto prop1 = PROPERTY_PAIR("prop1"); - auto prop2 = PROPERTY_PAIR("prop2"); + auto prop1 = PROPERTY_PAIR(dba, "prop1"); + auto prop2 = PROPERTY_PAIR(dba, "prop2"); dba.SetIndexCount(label1, prop1.second, 0); dba.SetIndexCount(label2, prop2.second, 0); - AstStorage storage; auto lit_1 = LITERAL(1); auto lit_2 = LITERAL(2); - auto *query = QUERY(SINGLE_QUERY( - MATCH(PATTERN(NODE("n", "label1")), PATTERN(NODE("m", "label2"))), - WHERE(AND(EQ(PROPERTY_LOOKUP("n", prop1), lit_1), EQ(PROPERTY_LOOKUP("m", prop2), lit_2))), RETURN("n", "m"))); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label1")), PATTERN(NODE("m", "label2"))), + WHERE(AND(EQ(PROPERTY_LOOKUP(dba, "n", prop1), lit_1), EQ(PROPERTY_LOOKUP(dba, "m", prop2), lit_2))), + RETURN("n", "m"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label1, prop1, lit_1), ExpectScanAllByLabelPropertyValue(label2, prop2, lit_2), ExpectProduce()); } @@ -1158,17 +1089,15 @@ TYPED_TEST(TestPlanner, WhereIndexedLabelPropertyRange) { auto label = dba.Label("label"); auto property = dba.Property("property"); dba.SetIndexCount(label, property, 0); - AstStorage storage; auto lit_42 = LITERAL(42); - auto n_prop = PROPERTY_LOOKUP("n", property); - auto check_planned_range = [&label, &property, &dba](const auto &rel_expr, auto lower_bound, auto upper_bound) { - // Shadow the first storage, so that the query is created in this one. - AstStorage storage; - storage.GetLabelIx("label"); - storage.GetPropertyIx("property"); + auto n_prop = PROPERTY_LOOKUP(dba, "n", property); + auto check_planned_range = [&, this](const auto &rel_expr, auto lower_bound, auto upper_bound) { + // Shadow the first this->storage, so that the query is created in this one. + this->storage.GetLabelIx("label"); + this->storage.GetPropertyIx("property"); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(rel_expr), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyRange(label, property, lower_bound, upper_bound), ExpectProduce()); }; @@ -1198,18 +1127,17 @@ TYPED_TEST(TestPlanner, WhereIndexedLabelPropertyRange) { TYPED_TEST(TestPlanner, WherePreferEqualityIndexOverRange) { // Test MATCH (n :label) WHERE n.property = 42 AND n.property > 0 RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); dba.SetIndexCount(label, property.second, 0); auto lit_42 = LITERAL(42); - auto *query = QUERY(SINGLE_QUERY( - MATCH(PATTERN(NODE("n", "label"))), - WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), lit_42), GREATER(PROPERTY_LOOKUP("n", property), LITERAL(0)))), - RETURN("n"))); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(EQ(PROPERTY_LOOKUP(dba, "n", property), lit_42), + GREATER(PROPERTY_LOOKUP(dba, "n", property), LITERAL(0)))), + RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectFilter(), ExpectProduce()); } @@ -1221,12 +1149,11 @@ TYPED_TEST(TestPlanner, UnableToUsePropertyIndex) { auto property = dba.Property("property"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, property, 0); - AstStorage storage; - auto *query = - QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(EQ(PROPERTY_LOOKUP("n", property), PROPERTY_LOOKUP("n", property))), RETURN("n"))); + auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(EQ(PROPERTY_LOOKUP(dba, "n", property), PROPERTY_LOOKUP(dba, "n", property))), + RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We can only get ScanAllByLabelIndex, because we are comparing properties // with those on the same node. CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); @@ -1236,16 +1163,15 @@ TYPED_TEST(TestPlanner, SecondPropertyIndex) { // Test MATCH (n :label), (m :label) WHERE m.property = n.property RETURN n FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, dba.Property("property"), 0); - AstStorage storage; - auto n_prop = PROPERTY_LOOKUP("n", property); - auto m_prop = PROPERTY_LOOKUP("m", property); + auto n_prop = PROPERTY_LOOKUP(dba, "n", property); + auto m_prop = PROPERTY_LOOKUP(dba, "m", property); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label")), PATTERN(NODE("m", "label"))), WHERE(EQ(m_prop, n_prop)), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), // Note: We are scanning for m, therefore property should equal n_prop. ExpectScanAllByLabelPropertyValue(label, property, n_prop), ExpectProduce()); @@ -1253,41 +1179,37 @@ TYPED_TEST(TestPlanner, SecondPropertyIndex) { TYPED_TEST(TestPlanner, ReturnSumGroupByAll) { // Test RETURN sum([1,2,3]), all(x in [1] where x = 1) - AstStorage storage; auto sum = SUM(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), false); auto *all = ALL("x", LIST(LITERAL(1)), WHERE(EQ(IDENT("x"), LITERAL(1)))); auto *query = QUERY(SINGLE_QUERY(RETURN(sum, AS("sum"), all, AS("all")))); auto aggr = ExpectAggregate({sum}, {all}); - CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, aggr, ExpectProduce()); } TYPED_TEST(TestPlanner, MatchExpandVariable) { // Test MATCH (n) -[r *..3]-> (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r"); edge->upper_bound_ = LITERAL(3); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchExpandVariableNoBounds) { // Test MATCH (n) -[r *]-> (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r"); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchExpandVariableInlinedFilter) { // Test MATCH (n) -[r :type * {prop: 42}]-> (m) RETURN r FakeDbAccessor dba; auto type = "type"; - auto prop = PROPERTY_PAIR("prop"); - AstStorage storage; + auto prop = PROPERTY_PAIR(dba, "prop"); auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type}); - std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); + std::get<0>(edge->properties_)[this->storage.GetPropertyIx(prop.first)] = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpandVariable(), // Filter is both inlined and post-expand ExpectFilter(), ExpectProduce()); } @@ -1296,25 +1218,23 @@ TYPED_TEST(TestPlanner, MatchExpandVariableNotInlinedFilter) { // Test MATCH (n) -[r :type * {prop: m.prop}]-> (m) RETURN r FakeDbAccessor dba; auto type = "type"; - auto prop = PROPERTY_PAIR("prop"); - AstStorage storage; + auto prop = PROPERTY_PAIR(dba, "prop"); auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH, {type}); - std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = EQ(PROPERTY_LOOKUP("m", prop), LITERAL(42)); + std::get<0>(edge->properties_)[this->storage.GetPropertyIx(prop.first)] = + EQ(PROPERTY_LOOKUP(dba, "m", prop), LITERAL(42)); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectExpandVariable(), ExpectFilter(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectExpandVariable(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchExpandVariableTotalWeightSymbol) { // Test MATCH p = (a {id: 0})-[r* wShortest (e, v | 1) total_weight]->(b) // RETURN * FakeDbAccessor dba; - AstStorage storage; - auto edge = EDGE_VARIABLE("r", Type::WEIGHTED_SHORTEST_PATH, Direction::BOTH, {}, nullptr, nullptr, nullptr, nullptr, nullptr, IDENT("total_weight")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("*"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); auto *root = dynamic_cast<Produce *>(&planner.plan()); ASSERT_TRUE(root); @@ -1334,23 +1254,21 @@ TYPED_TEST(TestPlanner, MatchExpandVariableTotalWeightSymbol) { TYPED_TEST(TestPlanner, UnwindMatchVariable) { // Test UNWIND [1,2,3] AS depth MATCH (n) -[r*d]-> (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::OUT); edge->lower_bound_ = IDENT("d"); edge->upper_bound_ = IDENT("d"); auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), AS("d")), MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectUnwind(), ExpectScanAll(), ExpectExpandVariable(), ExpectProduce()); } TYPED_TEST(TestPlanner, MatchBfs) { // Test MATCH (n) -[r:type *..10 (r, n|n)]-> (m) RETURN r FakeDbAccessor dba; - AstStorage storage; - auto edge_type = storage.GetEdgeTypeIx("type"); - auto *bfs = - storage.Create<memgraph::query::EdgeAtom>(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, - Direction::OUT, std::vector<memgraph::query::EdgeTypeIx>{edge_type}); + auto edge_type = this->storage.GetEdgeTypeIx("type"); + auto *bfs = this->storage.template Create<memgraph::query::EdgeAtom>( + IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, Direction::OUT, + std::vector<memgraph::query::EdgeTypeIx>{edge_type}); bfs->filter_lambda_.inner_edge = IDENT("r"); bfs->filter_lambda_.inner_node = IDENT("n"); bfs->filter_lambda_.expression = IDENT("n"); @@ -1358,7 +1276,7 @@ TYPED_TEST(TestPlanner, MatchBfs) { auto *as_r = NEXPR("r", IDENT("r")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN(as_r))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpandBfs(), ExpectProduce()); } @@ -1367,10 +1285,9 @@ TYPED_TEST(TestPlanner, MatchDoubleScanToExpandExisting) { FakeDbAccessor dba; auto label = "label"; dba.SetIndexCount(dba.Label(label), 0); - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m", label))), RETURN("r"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect 2x ScanAll and then Expand, since we are guessing that is // faster (due to low label index vertex count). CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectScanAllByLabel(), ExpectExpand(), ExpectProduce()); @@ -1384,12 +1301,11 @@ TYPED_TEST(TestPlanner, MatchScanToExpand) { // Fill vertices to the max + 1. dba.SetIndexCount(label, property, FLAGS_query_vertex_count_to_expand_existing + 1); dba.SetIndexCount(label, FLAGS_query_vertex_count_to_expand_existing + 1); - AstStorage storage; auto node_m = NODE("m", "label"); - std::get<0>(node_m->properties_)[storage.GetPropertyIx("property")] = LITERAL(1); + std::get<0>(node_m->properties_)[this->storage.GetPropertyIx("property")] = LITERAL(1); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), node_m)), RETURN("r"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); // We expect 1x ScanAll and then Expand, since we are guessing that // is faster (due to high label index vertex count). CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectExpand(), ExpectFilter(), ExpectProduce()); @@ -1398,28 +1314,27 @@ TYPED_TEST(TestPlanner, MatchScanToExpand) { TYPED_TEST(TestPlanner, MatchWhereAndSplit) { // Test MATCH (n) -[r]- (m) WHERE n.prop AND r.prop RETURN m FakeDbAccessor dba; - auto prop = PROPERTY_PAIR("prop"); - AstStorage storage; - auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), - WHERE(AND(PROPERTY_LOOKUP("n", prop), PROPERTY_LOOKUP("r", prop))), RETURN("m"))); + auto prop = PROPERTY_PAIR(dba, "prop"); + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), + WHERE(AND(PROPERTY_LOOKUP(dba, "n", prop), PROPERTY_LOOKUP(dba, "r", prop))), RETURN("m"))); // We expect `n.prop` filter right after scanning `n`. - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectFilter(), + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectFilter(), ExpectExpand(), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, ReturnAsteriskOmitsLambdaSymbols) { // Test MATCH (n) -[r* (ie, in | true)]- (m) RETURN * - AstStorage storage; + FakeDbAccessor dba; auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH); edge->filter_lambda_.inner_edge = IDENT("ie"); edge->filter_lambda_.inner_node = IDENT("in"); edge->filter_lambda_.expression = LITERAL(true); - auto ret = storage.Create<memgraph::query::Return>(); + auto ret = this->storage.template Create<memgraph::query::Return>(); ret->body_.all_identifiers = true; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), ret)); auto symbol_table = memgraph::query::MakeSymbolTable(query); - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); auto *produce = dynamic_cast<Produce *>(&planner.plan()); ASSERT_TRUE(produce); std::vector<std::string> outputs; @@ -1435,40 +1350,40 @@ TYPED_TEST(TestPlanner, ReturnAsteriskOmitsLambdaSymbols) { TYPED_TEST(TestPlanner, FilterRegexMatchIndex) { // Test MATCH (n :label) WHERE n.prop =~ "regex" RETURN n - AstStorage storage; FakeDbAccessor dba; auto prop = dba.Property("prop"); auto label = dba.Label("label"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, prop, 0); - auto *regex_match = storage.Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *regex_match = + this->storage.template Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP(dba, "n", prop), LITERAL("regex")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), WHERE(regex_match), RETURN("n"))); // We expect that we use index by property range where lower bound is an empty // string. Filter must still remain in place, because we don't have regex // based index. Bound lower_bound(LITERAL(""), Bound::Type::INCLUSIVE); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyRange(label, prop, lower_bound, std::nullopt), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, FilterRegexMatchPreferEqualityIndex) { // Test MATCH (n :label) WHERE n.prop =~ "regex" AND n.prop = 42 RETURN n - AstStorage storage; FakeDbAccessor dba; - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(dba, "prop"); auto label = dba.Label("label"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, prop.second, 0); - auto *regex_match = storage.Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *regex_match = + this->storage.template Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP(dba, "n", prop), LITERAL("regex")); auto *lit_42 = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(AND(regex_match, EQ(PROPERTY_LOOKUP("n", prop), lit_42))), RETURN("n"))); + WHERE(AND(regex_match, EQ(PROPERTY_LOOKUP(dba, "n", prop), lit_42))), RETURN("n"))); // We expect that we use index by property value equal to 42, because that's // much better than property range for regex matching. auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prop, lit_42), ExpectFilter(), ExpectProduce()); } @@ -1476,51 +1391,52 @@ TYPED_TEST(TestPlanner, FilterRegexMatchPreferEqualityIndex) { TYPED_TEST(TestPlanner, FilterRegexMatchPreferEqualityIndex2) { // Test MATCH (n :label) // WHERE n.prop =~ "regex" AND n.prop = 42 AND n.prop > 0 RETURN n - AstStorage storage; FakeDbAccessor dba; - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(dba, "prop"); auto label = dba.Label("label"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, prop.second, 0); - auto *regex_match = storage.Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *regex_match = + this->storage.template Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP(dba, "n", prop), LITERAL("regex")); auto *lit_42 = LITERAL(42); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(AND(AND(regex_match, EQ(PROPERTY_LOOKUP("n", prop), lit_42)), - GREATER(PROPERTY_LOOKUP("n", prop), LITERAL(0)))), + WHERE(AND(AND(regex_match, EQ(PROPERTY_LOOKUP(dba, "n", prop), lit_42)), + GREATER(PROPERTY_LOOKUP(dba, "n", prop), LITERAL(0)))), RETURN("n"))); // We expect that we use index by property value equal to 42, because that's // much better than property range. auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, prop, lit_42), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, FilterRegexMatchPreferRangeIndex) { // Test MATCH (n :label) WHERE n.prop =~ "regex" AND n.prop > 42 RETURN n - AstStorage storage; FakeDbAccessor dba; auto prop = dba.Property("prop"); auto label = dba.Label("label"); dba.SetIndexCount(label, 0); dba.SetIndexCount(label, prop, 0); - auto *regex_match = storage.Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP("n", prop), LITERAL("regex")); + auto *regex_match = + this->storage.template Create<memgraph::query::RegexMatch>(PROPERTY_LOOKUP(dba, "n", prop), LITERAL("regex")); auto *lit_42 = LITERAL(42); - auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(AND(regex_match, GREATER(PROPERTY_LOOKUP("n", prop), lit_42))), RETURN("n"))); + auto *query = + QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), + WHERE(AND(regex_match, GREATER(PROPERTY_LOOKUP(dba, "n", prop), lit_42))), RETURN("n"))); // We expect that we use index by property range on a concrete value (42), as // it is much better than using a range from empty string for regex matching. Bound lower_bound(lit_42, Bound::Type::EXCLUSIVE); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyRange(label, prop, lower_bound, std::nullopt), ExpectFilter(), ExpectProduce()); } TYPED_TEST(TestPlanner, CallProcedureStandalone) { // Test CALL proc(1,2,3) YIELD field AS result - AstStorage storage; - auto *ast_call = storage.Create<memgraph::query::CallProcedure>(); + FakeDbAccessor dba; + auto *ast_call = this->storage.template Create<memgraph::query::CallProcedure>(); ast_call->procedure_name_ = "proc"; ast_call->arguments_ = {LITERAL(1), LITERAL(2), LITERAL(3)}; ast_call->result_fields_ = {"field"}; @@ -1532,8 +1448,7 @@ TYPED_TEST(TestPlanner, CallProcedureStandalone) { for (const auto *ident : ast_call->result_identifiers_) { result_syms.push_back(symbol_table.at(*ident)); } - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan( planner.plan(), symbol_table, ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms)); @@ -1541,8 +1456,8 @@ TYPED_TEST(TestPlanner, CallProcedureStandalone) { TYPED_TEST(TestPlanner, CallProcedureAfterScanAll) { // Test MATCH (n) CALL proc(n) YIELD field AS result RETURN result - AstStorage storage; - auto *ast_call = storage.Create<memgraph::query::CallProcedure>(); + FakeDbAccessor dba; + auto *ast_call = this->storage.template Create<memgraph::query::CallProcedure>(); ast_call->procedure_name_ = "proc"; ast_call->arguments_ = {IDENT("n")}; ast_call->result_fields_ = {"field"}; @@ -1554,8 +1469,7 @@ TYPED_TEST(TestPlanner, CallProcedureAfterScanAll) { for (const auto *ident : ast_call->result_identifiers_) { result_syms.push_back(symbol_table.at(*ident)); } - FakeDbAccessor dba; - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms), ExpectProduce()); @@ -1563,22 +1477,21 @@ TYPED_TEST(TestPlanner, CallProcedureAfterScanAll) { TYPED_TEST(TestPlanner, CallProcedureBeforeScanAll) { // Test CALL proc() YIELD field MATCH (n) WHERE n.prop = field RETURN n - AstStorage storage; - auto *ast_call = storage.Create<memgraph::query::CallProcedure>(); + FakeDbAccessor dba; + auto *ast_call = this->storage.template Create<memgraph::query::CallProcedure>(); ast_call->procedure_name_ = "proc"; ast_call->result_fields_ = {"field"}; ast_call->result_identifiers_ = {IDENT("field")}; - FakeDbAccessor dba; auto property = dba.Property("prop"); auto *query = QUERY(SINGLE_QUERY(ast_call, MATCH(PATTERN(NODE("n"))), - WHERE(EQ(PROPERTY_LOOKUP("n", property), IDENT("field"))), RETURN("n"))); + WHERE(EQ(PROPERTY_LOOKUP(dba, "n", property), IDENT("field"))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); std::vector<Symbol> result_syms; result_syms.reserve(ast_call->result_identifiers_.size()); for (const auto *ident : ast_call->result_identifiers_) { result_syms.push_back(symbol_table.at(*ident)); } - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectCallProcedure(ast_call->procedure_name_, ast_call->arguments_, ast_call->result_fields_, result_syms), ExpectScanAll(), ExpectFilter(), ExpectProduce()); @@ -1586,57 +1499,53 @@ TYPED_TEST(TestPlanner, CallProcedureBeforeScanAll) { TYPED_TEST(TestPlanner, ScanAllById) { // Test MATCH (n) WHERE id(n) = 42 RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(EQ(FN("id", IDENT("n")), LITERAL(42))), RETURN("n"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAllById(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAllById(), ExpectProduce()); } TYPED_TEST(TestPlanner, ScanAllByIdExpandToExisting) { // Test MATCH (n)-[r]-(m) WHERE id(m) = 42 RETURN r - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), WHERE(EQ(FN("id", IDENT("m")), LITERAL(42))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpand(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpand(), ExpectProduce()); } TYPED_TEST(TestPlanner, BfsToExisting) { // Test MATCH (n)-[r *bfs]-(m) WHERE id(m) = 42 RETURN r - AstStorage storage; - auto *bfs = storage.Create<memgraph::query::EdgeAtom>(IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, - Direction::BOTH); + auto *bfs = this->storage.template Create<memgraph::query::EdgeAtom>( + IDENT("r"), memgraph::query::EdgeAtom::Type::BREADTH_FIRST, Direction::BOTH); bfs->filter_lambda_.inner_edge = IDENT("ie"); bfs->filter_lambda_.inner_node = IDENT("in"); bfs->filter_lambda_.expression = LITERAL(true); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), WHERE(EQ(FN("id", IDENT("m")), LITERAL(42))), RETURN("r"))); - CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpandBfs(), ExpectProduce()); + CheckPlan<TypeParam>(query, this->storage, ExpectScanAll(), ExpectScanAllById(), ExpectExpandBfs(), ExpectProduce()); } TYPED_TEST(TestPlanner, LabelPropertyInListValidOptimization) { // Test MATCH (n:label) WHERE n.property IN ['a'] RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); auto *lit_list_a = LIST(LITERAL('a')); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(IN_LIST(PROPERTY_LOOKUP("n", property), lit_list_a)), RETURN("n"))); + WHERE(IN_LIST(PROPERTY_LOOKUP(dba, "n", property), lit_list_a)), RETURN("n"))); { auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); } { dba.SetIndexCount(label, 1); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); } { dba.SetIndexCount(label, property.second, 1); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectScanAllByLabelPropertyValue(label, property, lit_list_a), ExpectProduce()); } @@ -1644,51 +1553,48 @@ TYPED_TEST(TestPlanner, LabelPropertyInListValidOptimization) { TYPED_TEST(TestPlanner, LabelPropertyInListWhereLabelPropertyOnLeftNotListOnRight) { // Test MATCH (n:label) WHERE n.property IN 'a' RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); auto *lit_a = LITERAL('a'); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(IN_LIST(PROPERTY_LOOKUP("n", property), lit_a)), RETURN("n"))); + WHERE(IN_LIST(PROPERTY_LOOKUP(dba, "n", property), lit_a)), RETURN("n"))); { dba.SetIndexCount(label, property.second, 1); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); } } TYPED_TEST(TestPlanner, LabelPropertyInListWhereLabelPropertyOnRight) { // Test MATCH (n:label) WHERE ['a'] IN n.property RETURN n - AstStorage storage; FakeDbAccessor dba; auto label = dba.Label("label"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); auto *lit_list_a = LIST(LITERAL('a')); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))), - WHERE(IN_LIST(lit_list_a, PROPERTY_LOOKUP("n", property))), RETURN("n"))); + WHERE(IN_LIST(lit_list_a, PROPERTY_LOOKUP(dba, "n", property))), RETURN("n"))); { auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectProduce()); } { dba.SetIndexCount(label, 1); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); } { dba.SetIndexCount(label, property.second, 1); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabel(), ExpectFilter(), ExpectProduce()); } } TYPED_TEST(TestPlanner, Foreach) { - AstStorage storage; FakeDbAccessor dba; { auto *i = NEXPR("i", IDENT("i")); @@ -1696,7 +1602,7 @@ TYPED_TEST(TestPlanner, Foreach) { auto create = ExpectCreateNode(); std::list<BaseOpChecker *> updates{&create}; std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectForeach(input, updates), ExpectEmptyResult()); } { auto *i = NEXPR("i", IDENT("i")); @@ -1704,16 +1610,16 @@ TYPED_TEST(TestPlanner, Foreach) { auto del = ExpectDelete(); std::list<BaseOpChecker *> updates{&del}; std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectForeach({input}, updates), ExpectEmptyResult()); } { auto prop = dba.Property("prop"); auto *i = NEXPR("i", IDENT("i")); - auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {SET(PROPERTY_LOOKUP("i", prop), LITERAL(10))}))); + auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {SET(PROPERTY_LOOKUP(dba, "i", prop), LITERAL(10))}))); auto set_prop = ExpectSetProperty(); std::list<BaseOpChecker *> updates{&set_prop}; std::list<BaseOpChecker *> input; - CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectForeach({input}, updates), ExpectEmptyResult()); } { auto *i = NEXPR("i", IDENT("i")); @@ -1725,7 +1631,7 @@ TYPED_TEST(TestPlanner, Foreach) { std::list<BaseOpChecker *> nested_updates{{&create, &del}}; auto nested_foreach = ExpectForeach(input, nested_updates); std::list<BaseOpChecker *> updates{&nested_foreach}; - CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectForeach(input, updates), ExpectEmptyResult()); } { auto *i = NEXPR("i", IDENT("i")); @@ -1737,7 +1643,7 @@ TYPED_TEST(TestPlanner, Foreach) { std::list<BaseOpChecker *> input{&input_op}; auto *query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); - CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult()); + CheckPlan<TypeParam>(query, this->storage, ExpectForeach(input, updates), ExpectEmptyResult()); } { @@ -1751,7 +1657,7 @@ TYPED_TEST(TestPlanner, Foreach) { auto *query = QUERY(SINGLE_QUERY(FOREACH(n, {MERGE(PATTERN(NODE("v", label_name)))}))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> on_match{new ExpectScanAllByLabel()}; std::list<BaseOpChecker *> on_create{new ExpectCreateNode()}; @@ -1767,10 +1673,8 @@ TYPED_TEST(TestPlanner, Foreach) { } TYPED_TEST(TestPlanner, Exists) { - AstStorage storage; - FakeDbAccessor dba; - // MATCH (n) WHERE exists((n)-[]-()) + FakeDbAccessor dba; { auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"))), @@ -1779,7 +1683,7 @@ TYPED_TEST(TestPlanner, Exists) { RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectLimit(), new ExpectEvaluatePatternFilter()}; CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), @@ -1797,7 +1701,7 @@ TYPED_TEST(TestPlanner, Exists) { RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(), new ExpectEvaluatePatternFilter()}; @@ -1818,7 +1722,7 @@ TYPED_TEST(TestPlanner, Exists) { RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> pattern_filter_with_types{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(), new ExpectEvaluatePatternFilter()}; std::list<BaseOpChecker *> pattern_filter_without_types{new ExpectExpand(), new ExpectLimit(), @@ -1840,11 +1744,11 @@ TYPED_TEST(TestPlanner, Exists) { MATCH(PATTERN(NODE("n"))), WHERE(AND(EXISTS(PATTERN(NODE("n"), EDGE("edge", memgraph::query::EdgeAtom::Direction::BOTH, {"TYPE"}, false), NODE("node", "Two", false))), - PROPERTY_LOOKUP("n", property))), + PROPERTY_LOOKUP(dba, "n", property))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> pattern_filter{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(), new ExpectEvaluatePatternFilter()}; @@ -1865,7 +1769,7 @@ TYPED_TEST(TestPlanner, Exists) { RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> pattern_filter_with_types{new ExpectExpand(), new ExpectFilter(), new ExpectLimit(), new ExpectEvaluatePatternFilter()}; std::list<BaseOpChecker *> pattern_filter_without_types{new ExpectExpand(), new ExpectLimit(), @@ -1882,16 +1786,14 @@ TYPED_TEST(TestPlanner, Exists) { } TYPED_TEST(TestPlanner, Subqueries) { - AstStorage storage; - FakeDbAccessor dba; - // MATCH (n) CALL { MATCH (m) RETURN (m) } RETURN n, m + FakeDbAccessor dba; { auto *subquery = SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), CALL_SUBQUERY(subquery), RETURN("m", "n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> subquery_plan{new ExpectScanAll(), new ExpectProduce()}; CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectApply(subquery_plan), ExpectProduce()); @@ -1905,7 +1807,7 @@ TYPED_TEST(TestPlanner, Subqueries) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), CALL_SUBQUERY(subquery), RETURN("m", "n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> subquery_plan{new ExpectScanAll(), new ExpectExpand(), new ExpectProduce()}; CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectApply(subquery_plan), ExpectProduce()); @@ -1917,11 +1819,11 @@ TYPED_TEST(TestPlanner, Subqueries) { { auto property = dba.Property("prop"); auto *subquery = SINGLE_QUERY(MATCH(PATTERN(NODE("p"), EDGE("r", Direction::OUT), NODE("s"))), - WHERE(EQ(PROPERTY_LOOKUP("s", property), LITERAL(2))), RETURN("p")); + WHERE(EQ(PROPERTY_LOOKUP(dba, "s", property), LITERAL(2))), RETURN("p")); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n", "p"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> subquery_plan{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter(), new ExpectProduce()}; @@ -1937,7 +1839,7 @@ TYPED_TEST(TestPlanner, Subqueries) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), CALL_SUBQUERY(subquery), RETURN("m", "n", "o"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> subquery_inside_subquery_plan{new ExpectScanAll(), new ExpectProduce()}; std::list<BaseOpChecker *> subquery_plan{new ExpectScanAll(), new ExpectApply(subquery_inside_subquery_plan), new ExpectProduce()}; @@ -1955,7 +1857,7 @@ TYPED_TEST(TestPlanner, Subqueries) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m"))), CALL_SUBQUERY(subquery), RETURN("m", "n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); - auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query); + auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query); std::list<BaseOpChecker *> left_subquery_part{new ExpectScanAll(), new ExpectProduce()}; std::list<BaseOpChecker *> right_subquery_part{new ExpectScanAll(), new ExpectProduce()}; diff --git a/tests/unit/query_plan_accumulate_aggregate.cpp b/tests/unit/query_plan_accumulate_aggregate.cpp index 7fb9e7987..e271e0f6a 100644 --- a/tests/unit/query_plan_accumulate_aggregate.cpp +++ b/tests/unit/query_plan_accumulate_aggregate.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,6 +14,7 @@ #include <memory> #include <vector> +#include "disk_test_utils.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -21,6 +22,8 @@ #include "query/exceptions.hpp" #include "query/plan/operator.hpp" #include "query_plan_common.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; @@ -28,7 +31,60 @@ using memgraph::query::test_common::ToIntList; using memgraph::query::test_common::ToIntMap; using testing::UnorderedElementsAre; -TEST(QueryPlan, Accumulate) { +template <typename StorageType> +class QueryPlanTest : public testing::Test { + public: + const std::string testSuite = "query_plan_accumulate_aggregate"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db = std::make_unique<StorageType>(config); + AstStorage storage; + + void TearDown() override { CleanStorageDirs(); } + + void CleanStorageDirs() { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + std::shared_ptr<Produce> MakeAggregationProduce(std::shared_ptr<LogicalOperator> input, SymbolTable &symbol_table, + const std::vector<Expression *> aggr_inputs, + const std::vector<Aggregation::Op> aggr_ops, + const std::vector<Expression *> group_by_exprs, + const std::vector<Symbol> remember, const bool distinct) { + // prepare all the aggregations + std::vector<Aggregate::Element> aggregates; + std::vector<NamedExpression *> named_expressions; + + auto aggr_inputs_it = aggr_inputs.begin(); + for (auto aggr_op : aggr_ops) { + // TODO change this from using IDENT to using AGGREGATION + // once AGGREGATION is handled properly in ExpressionEvaluation + auto aggr_sym = symbol_table.CreateSymbol("aggregation", true); + auto named_expr = + NEXPR("", IDENT("aggregation")->MapTo(aggr_sym))->MapTo(symbol_table.CreateSymbol("named_expression", true)); + named_expressions.push_back(named_expr); + // the key expression is only used in COLLECT_MAP + Expression *key_expr_ptr = aggr_op == Aggregation::Op::COLLECT_MAP ? LITERAL("key") : nullptr; + aggregates.emplace_back(Aggregate::Element{*aggr_inputs_it++, key_expr_ptr, aggr_op, aggr_sym, distinct}); + } + + // Produce will also evaluate group_by expressions and return them after the + // aggregations. + for (auto group_by_expr : group_by_exprs) { + auto named_expr = NEXPR("", group_by_expr)->MapTo(symbol_table.CreateSymbol("named_expression", true)); + named_expressions.push_back(named_expr); + } + auto aggregation = std::make_shared<Aggregate>(input, aggregates, group_by_exprs, remember); + return std::make_shared<Produce>(aggregation, named_expressions); + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; + +TYPED_TEST_CASE(QueryPlanTest, StorageTypes); + +TYPED_TEST(QueryPlanTest, Accumulate) { // simulate the following two query execution on an empty db // CREATE ({x:0})-[:T]->({x:0}) // MATCH (n)--(m) SET n.x = n.x + 1, m.x = m.x + 1 RETURN n.x, m.x @@ -36,9 +92,11 @@ TEST(QueryPlan, Accumulate) { // with accumulation we expect them to be [[2, 2], [2, 2]] auto check = [&](bool accumulate) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + this->db.reset(nullptr); + this->CleanStorageDirs(); + this->db = std::make_unique<TypeParam>(this->config); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("x"); auto v1 = dba.InsertVertex(); @@ -48,17 +106,16 @@ TEST(QueryPlan, Accumulate) { ASSERT_TRUE(dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("T")).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", false, memgraph::storage::View::OLD); auto one = LITERAL(1); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto set_n_p = std::make_shared<plan::SetProperty>(r_m.op_, prop, n_p, ADD(n_p, one)); - auto m_p = PROPERTY_LOOKUP(IDENT("m")->MapTo(r_m.node_sym_), prop); + auto m_p = PROPERTY_LOOKUP(dba, IDENT("m")->MapTo(r_m.node_sym_), prop); auto set_m_p = std::make_shared<plan::SetProperty>(set_n_p, prop, m_p, ADD(m_p, one)); std::shared_ptr<LogicalOperator> last_op = set_m_p; @@ -69,7 +126,7 @@ TEST(QueryPlan, Accumulate) { auto n_p_ne = NEXPR("n.p", n_p)->MapTo(symbol_table.CreateSymbol("n_p_ne", true)); auto m_p_ne = NEXPR("m.p", m_p)->MapTo(symbol_table.CreateSymbol("m_p_ne", true)); auto produce = MakeProduce(last_op, n_p_ne, m_p_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); std::vector<int> results_data; for (const auto &row : results) @@ -84,68 +141,36 @@ TEST(QueryPlan, Accumulate) { check(true); } -TEST(QueryPlan, AccumulateAdvance) { +TYPED_TEST(QueryPlanTest, AccumulateAdvance) { // we simulate 'CREATE (n) WITH n AS n MATCH (m) RETURN m' // to get correct results we need to advance the command auto check = [&](bool advance) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + this->db.reset(); + this->CleanStorageDirs(); + this->db = std::make_unique<TypeParam>(this->config); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; NodeCreationInfo node; node.symbol = symbol_table.CreateSymbol("n", true); auto create = std::make_shared<CreateNode>(nullptr, node); auto accumulate = std::make_shared<Accumulate>(create, std::vector<Symbol>{node.symbol}, advance); - auto match = MakeScanAll(storage, symbol_table, "m", accumulate); - auto context = MakeContext(storage, symbol_table, &dba); + auto match = MakeScanAll(this->storage, symbol_table, "m", accumulate); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(advance ? 1 : 0, PullAll(*match.op_, &context)); }; check(false); check(true); } -std::shared_ptr<Produce> MakeAggregationProduce(std::shared_ptr<LogicalOperator> input, SymbolTable &symbol_table, - AstStorage &storage, const std::vector<Expression *> aggr_inputs, - const std::vector<Aggregation::Op> aggr_ops, - const std::vector<Expression *> group_by_exprs, - const std::vector<Symbol> remember, const bool distinct) { - // prepare all the aggregations - std::vector<Aggregate::Element> aggregates; - std::vector<NamedExpression *> named_expressions; - - auto aggr_inputs_it = aggr_inputs.begin(); - for (auto aggr_op : aggr_ops) { - // TODO change this from using IDENT to using AGGREGATION - // once AGGREGATION is handled properly in ExpressionEvaluation - auto aggr_sym = symbol_table.CreateSymbol("aggregation", true); - auto named_expr = - NEXPR("", IDENT("aggregation")->MapTo(aggr_sym))->MapTo(symbol_table.CreateSymbol("named_expression", true)); - named_expressions.push_back(named_expr); - // the key expression is only used in COLLECT_MAP - Expression *key_expr_ptr = aggr_op == Aggregation::Op::COLLECT_MAP ? LITERAL("key") : nullptr; - aggregates.emplace_back(Aggregate::Element{*aggr_inputs_it++, key_expr_ptr, aggr_op, aggr_sym, distinct}); - } - - // Produce will also evaluate group_by expressions and return them after the - // aggregations. - for (auto group_by_expr : group_by_exprs) { - auto named_expr = NEXPR("", group_by_expr)->MapTo(symbol_table.CreateSymbol("named_expression", true)); - named_expressions.push_back(named_expr); - } - auto aggregation = std::make_shared<Aggregate>(input, aggregates, group_by_exprs, remember); - return std::make_shared<Produce>(aggregation, named_expressions); -} - /** Test fixture for all the aggregation ops in one return. */ -class QueryPlanAggregateOps : public ::testing::Test { +template <typename StorageType> +class QueryPlanAggregateOps : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - memgraph::storage::PropertyId prop = db.NameToProperty("prop"); + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; + memgraph::storage::PropertyId prop = this->db->NameToProperty("prop"); - AstStorage storage; SymbolTable symbol_table; void AddData() { @@ -171,23 +196,25 @@ class QueryPlanAggregateOps : public ::testing::Test { Aggregation::Op::MAX, Aggregation::Op::SUM, Aggregation::Op::AVG, Aggregation::Op::COLLECT_LIST, Aggregation::Op::COLLECT_MAP}) { // match all nodes and perform aggregations - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); std::vector<Expression *> aggregation_expressions(ops.size(), n_p); std::vector<Expression *> group_bys; if (with_group_by) group_bys.push_back(n_p); aggregation_expressions[0] = nullptr; auto produce = - MakeAggregationProduce(n.op_, symbol_table, storage, aggregation_expressions, ops, group_bys, {}, distinct); - auto context = MakeContext(storage, symbol_table, &dba); + this->MakeAggregationProduce(n.op_, symbol_table, aggregation_expressions, ops, group_bys, {}, distinct); + auto context = MakeContext(this->storage, symbol_table, &dba); return CollectProduce(*produce, &context); } }; -TEST_F(QueryPlanAggregateOps, WithData) { - AddData(); - auto results = AggregationResults(false, false); +TYPED_TEST_CASE(QueryPlanAggregateOps, StorageTypes); + +TYPED_TEST(QueryPlanAggregateOps, WithData) { + this->AddData(); + auto results = this->AggregationResults(false, false); ASSERT_EQ(results.size(), 1); ASSERT_EQ(results[0].size(), 8); @@ -220,48 +247,48 @@ TEST_F(QueryPlanAggregateOps, WithData) { EXPECT_FALSE(std::set<int>({5, 7, 12}).insert(map.begin()->second).second); } -TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) { +TYPED_TEST(QueryPlanAggregateOps, WithoutDataWithGroupBy) { { - auto results = AggregationResults(true, false, {Aggregation::Op::COUNT}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::COUNT}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int); EXPECT_EQ(results[0][0].ValueInt(), 0); } { - auto results = AggregationResults(true, false, {Aggregation::Op::SUM}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::SUM}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int); EXPECT_EQ(results[0][0].ValueInt(), 0); } { - auto results = AggregationResults(true, false, {Aggregation::Op::AVG}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::AVG}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, false, {Aggregation::Op::MIN}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::MIN}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, false, {Aggregation::Op::MAX}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::MAX}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, false, {Aggregation::Op::COLLECT_LIST}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::COLLECT_LIST}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::List); } { - auto results = AggregationResults(true, false, {Aggregation::Op::COLLECT_MAP}); + auto results = this->AggregationResults(true, false, {Aggregation::Op::COLLECT_MAP}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map); } } -TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) { - auto results = AggregationResults(false, false); +TYPED_TEST(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) { + auto results = this->AggregationResults(false, false); ASSERT_EQ(results.size(), 1); ASSERT_EQ(results[0].size(), 8); // count(*) @@ -286,13 +313,12 @@ TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) { EXPECT_EQ(ToIntMap(results[0][7]).size(), 0); } -TEST(QueryPlan, AggregateGroupByValues) { +TYPED_TEST(QueryPlanTest, AggregateGroupByValues) { // Tests that distinct groups are aggregated properly for values of all types. // Also test the "remember" part of the Aggregation API as final results are // obtained via a property lookup of a remembered node. - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // a vector of memgraph::storage::PropertyValue to be set as property values on vertices // most of them should result in a distinct group (commented where not) @@ -324,17 +350,16 @@ TEST(QueryPlan, AggregateGroupByValues) { ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, group_by_vals[i % group_by_vals.size()]).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // match all nodes and perform aggregations - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto produce = - MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, false); + this->MakeAggregationProduce(n.op_, symbol_table, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(results.size(), group_by_vals.size() - 2); std::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual> result_group_bys; @@ -350,13 +375,12 @@ TEST(QueryPlan, AggregateGroupByValues) { TypedValue::BoolEqual{})); } -TEST(QueryPlan, AggregateMultipleGroupBy) { +TYPED_TEST(QueryPlanTest, AggregateMultipleGroupBy) { // in this test we have 3 different properties that have different values // for different records and assert that we get the correct combination // of values in our groups - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto prop1 = dba.NameToProperty("prop1"); auto prop2 = dba.NameToProperty("prop2"); @@ -369,33 +393,30 @@ TEST(QueryPlan, AggregateMultipleGroupBy) { } dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // match all nodes and perform aggregations - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1); - auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop2); - auto n_p3 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop3); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p1 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop1); + auto n_p2 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop2); + auto n_p3 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop3); - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p1}, {Aggregation::Op::COUNT}, - {n_p1, n_p2, n_p3}, {n.sym_}, false); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {n_p1}, {Aggregation::Op::COUNT}, {n_p1, n_p2, n_p3}, + {n.sym_}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2 * 3 * 5); } -TEST(QueryPlan, AggregateNoInput) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, AggregateNoInput) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto two = LITERAL(2); - auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(nullptr, symbol_table, {two}, {Aggregation::Op::COUNT}, {}, {}, false); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(1, results.size()); EXPECT_EQ(1, results[0].size()); @@ -403,7 +424,7 @@ TEST(QueryPlan, AggregateNoInput) { EXPECT_EQ(1, results[0][0].ValueInt()); } -TEST(QueryPlan, AggregateCountEdgeCases) { +TYPED_TEST(QueryPlanTest, AggregateCountEdgeCases) { // tests for detected bugs in the COUNT aggregation behavior // ensure that COUNT returns correctly for // - 0 vertices in database @@ -412,22 +433,20 @@ TEST(QueryPlan, AggregateCountEdgeCases) { // - 2 vertices in database, property set on one // - 2 vertices in database, property set on both - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("prop"); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); // returns -1 when there are no results // otherwise returns MATCH (n) RETURN count(n.prop) auto count = [&]() { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {n_p}, {Aggregation::Op::COUNT}, {}, {}, false); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); if (results.size() == 0) return -1L; EXPECT_EQ(1, results.size()); @@ -462,13 +481,12 @@ TEST(QueryPlan, AggregateCountEdgeCases) { EXPECT_EQ(2, count()); } -TEST(QueryPlan, AggregateFirstValueTypes) { +TYPED_TEST(QueryPlanTest, AggregateFirstValueTypes) { // testing exceptions that get emitted by the first-value // type check - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto prop_string = dba.NameToProperty("string"); @@ -477,17 +495,16 @@ TEST(QueryPlan, AggregateFirstValueTypes) { ASSERT_TRUE(v1.SetProperty(prop_int, memgraph::storage::PropertyValue(12)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_prop_string = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_string); - auto n_prop_int = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_int); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_prop_string = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop_string); + auto n_prop_int = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop_int); auto n_id = n_prop_string->expression_; auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {expression}, {aggr_op}, {}, {}, false); + auto context = MakeContext(this->storage, symbol_table, &dba); CollectProduce(*produce, &context); }; @@ -515,14 +532,13 @@ TEST(QueryPlan, AggregateFirstValueTypes) { aggregate(n_prop_int, Aggregation::Op::COLLECT_MAP); } -TEST(QueryPlan, AggregateTypes) { +TYPED_TEST(QueryPlanTest, AggregateTypes) { // testing exceptions that can get emitted by an aggregation // does not check all combinations that can result in an exception // (that logic is defined and tested by TypedValue) - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto p1 = dba.NameToProperty("p1"); // has only string props ASSERT_TRUE(dba.InsertVertex().SetProperty(p1, memgraph::storage::PropertyValue("string")).HasValue()); @@ -532,16 +548,15 @@ TEST(QueryPlan, AggregateTypes) { ASSERT_TRUE(dba.InsertVertex().SetProperty(p2, memgraph::storage::PropertyValue(true)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1); - auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p1 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p1); + auto n_p2 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p2); auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {expression}, {aggr_op}, {}, {}, false); + auto context = MakeContext(this->storage, symbol_table, &dba); CollectProduce(*produce, &context); }; @@ -574,15 +589,13 @@ TEST(QueryPlan, AggregateTypes) { EXPECT_THROW(aggregate(n_p2, Aggregation::Op::SUM), QueryRuntimeException); } -TEST(QueryPlan, Unwind) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, Unwind) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; // UNWIND [ [1, true, "x"], [], ["bla"] ] AS x UNWIND x as y RETURN x, y - auto input_expr = storage.Create<PrimitiveLiteral>(std::vector<memgraph::storage::PropertyValue>{ + auto input_expr = this->storage.template Create<PrimitiveLiteral>(std::vector<memgraph::storage::PropertyValue>{ memgraph::storage::PropertyValue(std::vector<memgraph::storage::PropertyValue>{ memgraph::storage::PropertyValue(1), memgraph::storage::PropertyValue(true), memgraph::storage::PropertyValue("x")}), @@ -600,7 +613,7 @@ TEST(QueryPlan, Unwind) { auto y_ne = NEXPR("y", IDENT("y")->MapTo(y))->MapTo(symbol_table.CreateSymbol("y_ne", true)); auto produce = MakeProduce(unwind_1, x_ne, y_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(4, results.size()); const std::vector<int> expected_x_card{3, 3, 3, 1}; @@ -617,9 +630,9 @@ TEST(QueryPlan, Unwind) { } } -TEST_F(QueryPlanAggregateOps, WithDataDistinct) { - AddData(); - auto results = AggregationResults(false, true); +TYPED_TEST(QueryPlanAggregateOps, WithDataDistinct) { + this->AddData(); + auto results = this->AggregationResults(false, true); ASSERT_EQ(results.size(), 1); ASSERT_EQ(results[0].size(), 8); @@ -652,48 +665,48 @@ TEST_F(QueryPlanAggregateOps, WithDataDistinct) { EXPECT_FALSE(std::set<int>({5, 7, 12}).insert(map.begin()->second).second); } -TEST_F(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithGroupBy) { +TYPED_TEST(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithGroupBy) { { - auto results = AggregationResults(true, true, {Aggregation::Op::COUNT}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::COUNT}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int); EXPECT_EQ(results[0][0].ValueInt(), 0); } { - auto results = AggregationResults(true, true, {Aggregation::Op::SUM}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::SUM}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int); EXPECT_EQ(results[0][0].ValueInt(), 0); } { - auto results = AggregationResults(true, true, {Aggregation::Op::AVG}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::AVG}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, true, {Aggregation::Op::MIN}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::MIN}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, true, {Aggregation::Op::MAX}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::MAX}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } { - auto results = AggregationResults(true, true, {Aggregation::Op::COLLECT_LIST}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::COLLECT_LIST}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::List); } { - auto results = AggregationResults(true, true, {Aggregation::Op::COLLECT_MAP}); + auto results = this->AggregationResults(true, true, {Aggregation::Op::COLLECT_MAP}); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map); } } -TEST_F(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithoutGroupBy) { - auto results = AggregationResults(false, true); +TYPED_TEST(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithoutGroupBy) { + auto results = this->AggregationResults(false, true); ASSERT_EQ(results.size(), 1); ASSERT_EQ(results[0].size(), 8); // count(*) @@ -718,13 +731,12 @@ TEST_F(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithoutGroupBy) { EXPECT_EQ(ToIntMap(results[0][7]).size(), 0); } -TEST(QueryPlan, AggregateGroupByValuesWithDistinct) { +TYPED_TEST(QueryPlanTest, AggregateGroupByValuesWithDistinct) { // Tests that distinct groups are aggregated properly for values of all types. // Also test the "remember" part of the Aggregation API as final results are // obtained via a property lookup of a remembered node. - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // a vector of memgraph::storage::PropertyValue to be set as property values on vertices // most of them should result in a distinct group (commented where not) @@ -756,17 +768,16 @@ TEST(QueryPlan, AggregateGroupByValuesWithDistinct) { ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, group_by_vals[i % group_by_vals.size()]).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // match all nodes and perform aggregations - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto produce = - MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, true); + this->MakeAggregationProduce(n.op_, symbol_table, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(results.size(), group_by_vals.size() - 2); std::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual> result_group_bys; @@ -785,13 +796,12 @@ TEST(QueryPlan, AggregateGroupByValuesWithDistinct) { TypedValue::BoolEqual{})); } -TEST(QueryPlan, AggregateMultipleGroupByWithDistinct) { +TYPED_TEST(QueryPlanTest, AggregateMultipleGroupByWithDistinct) { // in this test we have 3 different properties that have different values // for different records and assert that we get the correct combination // of values in our groups - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto prop1 = dba.NameToProperty("prop1"); auto prop2 = dba.NameToProperty("prop2"); @@ -804,34 +814,31 @@ TEST(QueryPlan, AggregateMultipleGroupByWithDistinct) { } dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // match all nodes and perform aggregations - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1); - auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop2); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p1 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop1); + auto n_p2 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop2); - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p1}, {Aggregation::Op::COUNT}, {n_p1, n_p2}, - {n.sym_}, true); + auto produce = + this->MakeAggregationProduce(n.op_, symbol_table, {n_p1}, {Aggregation::Op::COUNT}, {n_p1, n_p2}, {n.sym_}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); for (const auto &row : results) { ASSERT_EQ(1, row[0].ValueInt()); } } -TEST(QueryPlan, AggregateNoInputWithDistinct) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, AggregateNoInputWithDistinct) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto two = LITERAL(2); - auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(nullptr, symbol_table, {two}, {Aggregation::Op::COUNT}, {}, {}, true); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(1, results.size()); EXPECT_EQ(1, results[0].size()); @@ -839,7 +846,7 @@ TEST(QueryPlan, AggregateNoInputWithDistinct) { EXPECT_EQ(1, results[0][0].ValueInt()); } -TEST(QueryPlan, AggregateCountEdgeCasesWithDistinct) { +TYPED_TEST(QueryPlanTest, AggregateCountEdgeCasesWithDistinct) { // tests for detected bugs in the COUNT aggregation behavior // ensure that COUNT returns correctly for // - 0 vertices in database @@ -848,22 +855,20 @@ TEST(QueryPlan, AggregateCountEdgeCasesWithDistinct) { // - 2 vertices in database, property set on one // - 2 vertices in database, property set on both - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("prop"); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); // returns -1 when there are no results // otherwise returns MATCH (n) RETURN count(n.prop) auto count = [&]() { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {n_p}, {Aggregation::Op::COUNT}, {}, {}, true); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); if (results.size() == 0) return -1L; EXPECT_EQ(1, results.size()); @@ -898,13 +903,12 @@ TEST(QueryPlan, AggregateCountEdgeCasesWithDistinct) { EXPECT_EQ(1, count()); } -TEST(QueryPlan, AggregateFirstValueTypesWithDistinct) { +TYPED_TEST(QueryPlanTest, AggregateFirstValueTypesWithDistinct) { // testing exceptions that get emitted by the first-value // type check - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto prop_string = dba.NameToProperty("string"); @@ -913,17 +917,16 @@ TEST(QueryPlan, AggregateFirstValueTypesWithDistinct) { ASSERT_TRUE(v1.SetProperty(prop_int, memgraph::storage::PropertyValue(12)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_prop_string = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_string); - auto n_prop_int = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_int); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_prop_string = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop_string); + auto n_prop_int = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop_int); auto n_id = n_prop_string->expression_; auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {expression}, {aggr_op}, {}, {}, true); + auto context = MakeContext(this->storage, symbol_table, &dba); CollectProduce(*produce, &context); }; @@ -951,14 +954,13 @@ TEST(QueryPlan, AggregateFirstValueTypesWithDistinct) { aggregate(n_prop_int, Aggregation::Op::COLLECT_MAP); } -TEST(QueryPlan, AggregateTypesWithDistinct) { +TYPED_TEST(QueryPlanTest, AggregateTypesWithDistinct) { // testing exceptions that can get emitted by an aggregation // does not check all combinations that can result in an exception // (that logic is defined and tested by TypedValue) - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto p1 = dba.NameToProperty("p1"); // has only string props ASSERT_TRUE(dba.InsertVertex().SetProperty(p1, memgraph::storage::PropertyValue("string")).HasValue()); @@ -968,16 +970,15 @@ TEST(QueryPlan, AggregateTypesWithDistinct) { ASSERT_TRUE(dba.InsertVertex().SetProperty(p2, memgraph::storage::PropertyValue(true)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1); - auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p1 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p1); + auto n_p2 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p2); auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) { - auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, true); - auto context = MakeContext(storage, symbol_table, &dba); + auto produce = this->MakeAggregationProduce(n.op_, symbol_table, {expression}, {aggr_op}, {}, {}, true); + auto context = MakeContext(this->storage, symbol_table, &dba); CollectProduce(*produce, &context); }; diff --git a/tests/unit/query_plan_bag_semantics.cpp b/tests/unit/query_plan_bag_semantics.cpp index f0b0916f4..6b1c7ab64 100644 --- a/tests/unit/query_plan_bag_semantics.cpp +++ b/tests/unit/query_plan_bag_semantics.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -19,30 +19,48 @@ #include <memory> #include <vector> +#include "disk_test_utils.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "query/context.hpp" #include "query/exceptions.hpp" #include "query/plan/operator.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "query_plan_common.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; -TEST(QueryPlan, Skip) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - +template <typename StorageType> +class QueryPlanTest : public testing::Test { + public: + const std::string testSuite = "query_plan_bag_semantics"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db = std::make_unique<StorageType>(config); AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlanTest, StorageTypes); + +TYPED_TEST(QueryPlanTest, Skip) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n1"); + auto n = MakeScanAll(this->storage, symbol_table, "n1"); auto skip = std::make_shared<plan::Skip>(n.op_, LITERAL(2)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(0, PullAll(*skip, &context)); dba.InsertVertex(); @@ -62,18 +80,15 @@ TEST(QueryPlan, Skip) { EXPECT_EQ(11, PullAll(*skip, &context)); } -TEST(QueryPlan, Limit) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - - AstStorage storage; +TYPED_TEST(QueryPlanTest, Limit) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n1"); + auto n = MakeScanAll(this->storage, symbol_table, "n1"); auto skip = std::make_shared<plan::Limit>(n.op_, LITERAL(2)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(0, PullAll(*skip, &context)); dba.InsertVertex(); @@ -93,37 +108,33 @@ TEST(QueryPlan, Limit) { EXPECT_EQ(2, PullAll(*skip, &context)); } -TEST(QueryPlan, CreateLimit) { +TYPED_TEST(QueryPlanTest, CreateLimit) { // CREATE (n), (m) // MATCH (n) CREATE (m) LIMIT 1 // in the end we need to have 3 vertices in the db - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.InsertVertex(); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n1"); + auto n = MakeScanAll(this->storage, symbol_table, "n1"); NodeCreationInfo m; m.symbol = symbol_table.CreateSymbol("m", true); auto c = std::make_shared<CreateNode>(n.op_, m); auto skip = std::make_shared<plan::Limit>(c, LITERAL(1)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*skip, &context)); dba.AdvanceCommand(); EXPECT_EQ(3, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); } -TEST(QueryPlan, OrderBy) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, OrderBy) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = dba.NameToProperty("prop"); @@ -179,24 +190,22 @@ TEST(QueryPlan, OrderBy) { dba.AdvanceCommand(); // order by and collect results - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto order_by = std::make_shared<plan::OrderBy>(n.op_, std::vector<SortItem>{{order_value_pair.first, n_p}}, std::vector<Symbol>{n.sym_}); auto n_p_ne = NEXPR("n.p", n_p)->MapTo(symbol_table.CreateSymbol("n.p", true)); auto produce = MakeProduce(order_by, n_p_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(values.size(), results.size()); for (int j = 0; j < results.size(); ++j) EXPECT_TRUE(TypedValue::BoolEqual{}(results[j][0], values[j])); } } -TEST(QueryPlan, OrderByMultiple) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, OrderByMultiple) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto p1 = dba.NameToProperty("p1"); @@ -218,9 +227,9 @@ TEST(QueryPlan, OrderByMultiple) { dba.AdvanceCommand(); // order by and collect results - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1); - auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p1 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p1); + auto n_p2 = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), p2); // order the results so we get // (p1: 0, p2: N-1) // (p1: 0, p2: N-2) @@ -235,7 +244,7 @@ TEST(QueryPlan, OrderByMultiple) { auto n_p1_ne = NEXPR("n.p1", n_p1)->MapTo(symbol_table.CreateSymbol("n.p1", true)); auto n_p2_ne = NEXPR("n.p2", n_p2)->MapTo(symbol_table.CreateSymbol("n.p2", true)); auto produce = MakeProduce(order_by, n_p1_ne, n_p2_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(N * N, results.size()); for (int j = 0; j < N * N; ++j) { @@ -246,11 +255,9 @@ TEST(QueryPlan, OrderByMultiple) { } } -TEST(QueryPlan, OrderByExceptions) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlanTest, OrderByExceptions) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = dba.NameToProperty("prop"); @@ -292,11 +299,11 @@ TEST(QueryPlan, OrderByExceptions) { memgraph::storage::PropertyValue::Type::Null); // order by and expect an exception - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto order_by = std::make_shared<plan::OrderBy>(n.op_, std::vector<SortItem>{{Ordering::ASC, n_p}}, std::vector<Symbol>{}); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*order_by, &context), QueryRuntimeException); } } diff --git a/tests/unit/query_plan_common.hpp b/tests/unit/query_plan_common.hpp index 975099a0e..cf5f2224d 100644 --- a/tests/unit/query_plan_common.hpp +++ b/tests/unit/query_plan_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -83,7 +83,9 @@ int PullAll(const LogicalOperator &logical_op, ExecutionContext *context) { Frame frame(context->symbol_table.max_position()); auto cursor = logical_op.MakeCursor(memgraph::utils::NewDeleteResource()); int count = 0; - while (cursor->Pull(frame, *context)) count++; + while (cursor->Pull(frame, *context)) { + count++; + } return count; } @@ -107,7 +109,7 @@ struct ScanAllTuple { ScanAllTuple MakeScanAll(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier, std::shared_ptr<LogicalOperator> input = {nullptr}, memgraph::storage::View view = memgraph::storage::View::OLD) { - auto node = NODE(identifier); + auto node = memgraph::query::test_common::GetNode(storage, identifier); auto symbol = symbol_table.CreateSymbol(identifier, true); node->identifier_->MapTo(symbol); auto logical_op = std::make_shared<ScanAll>(input, symbol, view); @@ -123,7 +125,7 @@ ScanAllTuple MakeScanAll(AstStorage &storage, SymbolTable &symbol_table, const s ScanAllTuple MakeScanAllByLabel(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier, memgraph::storage::LabelId label, std::shared_ptr<LogicalOperator> input = {nullptr}, memgraph::storage::View view = memgraph::storage::View::OLD) { - auto node = NODE(identifier); + auto node = memgraph::query::test_common::GetNode(storage, identifier); auto symbol = symbol_table.CreateSymbol(identifier, true); node->identifier_->MapTo(symbol); auto logical_op = std::make_shared<ScanAllByLabel>(input, symbol, label, view); @@ -142,7 +144,7 @@ ScanAllTuple MakeScanAllByLabelPropertyRange(AstStorage &storage, SymbolTable &s std::optional<Bound> upper_bound, std::shared_ptr<LogicalOperator> input = {nullptr}, memgraph::storage::View view = memgraph::storage::View::OLD) { - auto node = NODE(identifier); + auto node = memgraph::query::test_common::GetNode(storage, identifier); auto symbol = symbol_table.CreateSymbol(identifier, true); node->identifier_->MapTo(symbol); auto logical_op = std::make_shared<ScanAllByLabelPropertyRange>(input, symbol, label, property, property_name, @@ -161,7 +163,7 @@ ScanAllTuple MakeScanAllByLabelPropertyValue(AstStorage &storage, SymbolTable &s const std::string &property_name, Expression *value, std::shared_ptr<LogicalOperator> input = {nullptr}, memgraph::storage::View view = memgraph::storage::View::OLD) { - auto node = NODE(identifier); + auto node = memgraph::query::test_common::GetNode(storage, identifier); auto symbol = symbol_table.CreateSymbol(identifier, true); node->identifier_->MapTo(symbol); auto logical_op = @@ -181,11 +183,11 @@ ExpandTuple MakeExpand(AstStorage &storage, SymbolTable &symbol_table, std::shar Symbol input_symbol, const std::string &edge_identifier, EdgeAtom::Direction direction, const std::vector<memgraph::storage::EdgeTypeId> &edge_types, const std::string &node_identifier, bool existing_node, memgraph::storage::View view) { - auto edge = EDGE(edge_identifier, direction); + auto edge = memgraph::query::test_common::GetEdge(storage, edge_identifier, direction); auto edge_sym = symbol_table.CreateSymbol(edge_identifier, true); edge->identifier_->MapTo(edge_sym); - auto node = NODE(node_identifier); + auto node = memgraph::query::test_common::GetNode(storage, node_identifier); auto node_sym = symbol_table.CreateSymbol(node_identifier, true); node->identifier_->MapTo(node_sym); @@ -219,6 +221,7 @@ auto CountIterable(TIterable &&iterable) { inline uint64_t CountEdges(memgraph::query::DbAccessor *dba, memgraph::storage::View view) { uint64_t count = 0; for (auto vertex : dba->Vertices(view)) { + dba->PrefetchOutEdges(vertex); auto maybe_edges = vertex.OutEdges(view); MG_ASSERT(maybe_edges.HasValue()); count += CountIterable(*maybe_edges); diff --git a/tests/unit/query_plan_create_set_remove_delete.cpp b/tests/unit/query_plan_create_set_remove_delete.cpp index 54c643ae1..088f6f8cc 100644 --- a/tests/unit/query_plan_create_set_remove_delete.cpp +++ b/tests/unit/query_plan_create_set_remove_delete.cpp @@ -16,6 +16,7 @@ #include <vector> #include "auth/models.hpp" +#include "disk_test_utils.hpp" #include "glue/auth_checker.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -23,28 +24,47 @@ #include "license/license.hpp" #include "query/context.hpp" #include "query/exceptions.hpp" +#include "query/frontend/ast/ast.hpp" #include "query/interpret/frame.hpp" #include "query/plan/operator.hpp" #include "query_plan_common.hpp" +#include "storage/v2/disk/storage.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/property_value.hpp" +#include "storage/v2/storage.hpp" #include "storage/v2/vertex_accessor.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; -TEST(QueryPlan, CreateNodeWithAttributes) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +template <typename StorageType> +class QueryPlanTest : public testing::Test { + public: + const std::string testSuite = "query_plan_create_set_remove_delete"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db = std::make_unique<StorageType>(config); + AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlanTest, StorageTypes); + +TYPED_TEST(QueryPlanTest, CreateNodeWithAttributes) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label = dba.NameToLabel("Person"); - auto property = PROPERTY_PAIR("prop"); + auto property = PROPERTY_PAIR(dba, "prop"); - AstStorage storage; SymbolTable symbol_table; - NodeCreationInfo node; node.symbol = symbol_table.CreateSymbol("n", true); node.labels.emplace_back(label); @@ -52,7 +72,7 @@ TEST(QueryPlan, CreateNodeWithAttributes) { .emplace_back(property.second, LITERAL(42)); auto create = std::make_shared<CreateNode>(nullptr, node); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*create, &context); dba.AdvanceCommand(); @@ -79,26 +99,24 @@ TEST(QueryPlan, CreateNodeWithAttributes) { } #ifdef MG_ENTERPRISE -TEST(QueryPlan, FineGrainedCreateNodeWithAttributes) { +TYPED_TEST(QueryPlanTest, FineGrainedCreateNodeWithAttributes) { memgraph::license::global_license_checker.EnableTesting(); - memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; - memgraph::storage::Storage db; - auto dba = db.Access(); - DbAccessor execution_dba(&dba); - const auto label = dba.NameToLabel("label1"); + auto dba = this->db->Access(); + DbAccessor execution_dba(dba.get()); + const auto label = dba->NameToLabel("label1"); const auto property = memgraph::storage::PropertyId::FromInt(1); memgraph::query::plan::NodeCreationInfo node; std::get<std::vector<std::pair<memgraph::storage::PropertyId, Expression *>>>(node.properties) - .emplace_back(property, ast.Create<PrimitiveLiteral>(42)); + .emplace_back(property, this->storage.template Create<PrimitiveLiteral>(42)); node.symbol = symbol_table.CreateSymbol("n", true); node.labels.emplace_back(label); const auto test_create = [&](memgraph::auth::User &user) { memgraph::glue::FineGrainedAuthChecker auth_checker{user, &execution_dba}; - auto context = MakeContextWithFineGrainedChecker(ast, symbol_table, &execution_dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &execution_dba, &auth_checker); auto create = std::make_shared<CreateNode>(nullptr, node); return PullAll(*create, &context); @@ -122,18 +140,15 @@ TEST(QueryPlan, FineGrainedCreateNodeWithAttributes) { } #endif -TEST(QueryPlan, CreateReturn) { +TYPED_TEST(QueryPlanTest, CreateReturn) { // test CREATE (n:Person {age: 42}) RETURN n, n.age - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label = dba.NameToLabel("Person"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); - AstStorage storage; SymbolTable symbol_table; - NodeCreationInfo node; node.symbol = symbol_table.CreateSymbol("n", true); node.labels.emplace_back(label); @@ -143,11 +158,11 @@ TEST(QueryPlan, CreateReturn) { auto create = std::make_shared<CreateNode>(nullptr, node); auto named_expr_n = NEXPR("n", IDENT("n")->MapTo(node.symbol))->MapTo(symbol_table.CreateSymbol("named_expr_n", true)); - auto prop_lookup = PROPERTY_LOOKUP(IDENT("n")->MapTo(node.symbol), property); + auto prop_lookup = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(node.symbol), property); auto named_expr_n_p = NEXPR("n", prop_lookup)->MapTo(symbol_table.CreateSymbol("named_expr_n_p", true)); auto produce = MakeProduce(create, named_expr_n, named_expr_n_p); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(1, results.size()); EXPECT_EQ(2, results[0].size()); @@ -163,20 +178,17 @@ TEST(QueryPlan, CreateReturn) { } #ifdef MG_ENTERPRISE -TEST(QueryPlan, FineGrainedCreateReturn) { +TYPED_TEST(QueryPlanTest, FineGrainedCreateReturn) { memgraph::license::global_license_checker.EnableTesting(); // test CREATE (n:Person {age: 42}) RETURN n, n.age - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); const auto label = dba.NameToLabel("label"); - const auto property = PROPERTY_PAIR("property"); + const auto property = PROPERTY_PAIR(dba, "property"); - AstStorage storage; SymbolTable symbol_table; - NodeCreationInfo node; node.symbol = symbol_table.CreateSymbol("n", true); node.labels.emplace_back(label); @@ -186,7 +198,7 @@ TEST(QueryPlan, FineGrainedCreateReturn) { auto create = std::make_shared<CreateNode>(nullptr, node); auto named_expr_n = NEXPR("n", IDENT("n")->MapTo(node.symbol))->MapTo(symbol_table.CreateSymbol("named_expr_n", true)); - auto prop_lookup = PROPERTY_LOOKUP(IDENT("n")->MapTo(node.symbol), property); + auto prop_lookup = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(node.symbol), property); auto named_expr_n_p = NEXPR("n", prop_lookup)->MapTo(symbol_table.CreateSymbol("named_expr_n_p", true)); auto produce = MakeProduce(create, named_expr_n, named_expr_n_p); @@ -197,7 +209,7 @@ TEST(QueryPlan, FineGrainedCreateReturn) { user.fine_grained_access_handler().label_permissions().Grant("label", memgraph::auth::FineGrainedPermission::CREATE_DELETE); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); auto results = CollectProduce(*produce, &context); EXPECT_EQ(1, results.size()); EXPECT_EQ(2, results[0].size()); @@ -218,24 +230,22 @@ TEST(QueryPlan, FineGrainedCreateReturn) { user.fine_grained_access_handler().label_permissions().Grant("label", memgraph::auth::FineGrainedPermission::UPDATE); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); ASSERT_THROW(CollectProduce(*produce, &context), QueryRuntimeException); } } #endif -TEST(QueryPlan, CreateExpand) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, CreateExpand) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label_node_1 = dba.NameToLabel("Node1"); memgraph::storage::LabelId label_node_2 = dba.NameToLabel("Node2"); - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); SymbolTable symbol_table; - AstStorage storage; auto test_create_path = [&](bool cycle, int expected_nodes_created, int expected_edges_created) { int before_v = CountIterable(dba.Vertices(memgraph::storage::View::OLD)); @@ -262,7 +272,7 @@ TEST(QueryPlan, CreateExpand) { auto create_op = std::make_shared<CreateNode>(nullptr, n); auto create_expand = std::make_shared<CreateExpand>(m, r, create_op, n.symbol, cycle); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*create_expand, &context); dba.AdvanceCommand(); @@ -291,6 +301,7 @@ TEST(QueryPlan, CreateExpand) { } for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + dba.PrefetchOutEdges(vertex); auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD); MG_ASSERT(maybe_edges.HasValue()); for (auto edge : *maybe_edges) { @@ -302,12 +313,11 @@ TEST(QueryPlan, CreateExpand) { } #ifdef MG_ENTERPRISE -class CreateExpandWithAuthFixture : public testing::Test { +template <typename StorageType> +class CreateExpandWithAuthFixture : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } @@ -315,7 +325,7 @@ class CreateExpandWithAuthFixture : public testing::Test { void ExecuteCreateExpand(bool cycle, memgraph::auth::User &user) { const auto label_node_1 = dba.NameToLabel("Node1"); const auto label_node_2 = dba.NameToLabel("Node2"); - const auto property = PROPERTY_PAIR("property"); + const auto property = PROPERTY_PAIR(dba, "property"); const auto edge_type = dba.NameToEdgeType("edge_type"); // data for the first node @@ -340,7 +350,7 @@ class CreateExpandWithAuthFixture : public testing::Test { auto create_op = std::make_shared<CreateNode>(nullptr, n); auto create_expand = std::make_shared<CreateExpand>(m, r, create_op, n.symbol, cycle); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*create_expand, &context); dba.AdvanceCommand(); } @@ -351,38 +361,40 @@ class CreateExpandWithAuthFixture : public testing::Test { } }; -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithNoGrantsOnCreateDelete) { +TYPED_TEST_CASE(CreateExpandWithAuthFixture, StorageTypes); + +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithNoGrantsOnCreateDelete) { // All labels denied, All edge types denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteCreateExpand(false, user), QueryRuntimeException); - ASSERT_THROW(ExecuteCreateExpand(true, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(false, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(true, user), QueryRuntimeException); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithLabelsGrantedOnly) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithLabelsGrantedOnly) { // All labels granted, All edge types denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteCreateExpand(false, user), QueryRuntimeException); - ASSERT_THROW(ExecuteCreateExpand(true, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(false, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(true, user), QueryRuntimeException); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithEdgeTypesGrantedOnly) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithEdgeTypesGrantedOnly) { // All labels denied, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ASSERT_THROW(ExecuteCreateExpand(false, user), QueryRuntimeException); - ASSERT_THROW(ExecuteCreateExpand(true, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(false, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(true, user), QueryRuntimeException); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithFirstLabelGranted) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithFirstLabelGranted) { // First label granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("Node1", @@ -392,11 +404,11 @@ TEST_F(CreateExpandWithAuthFixture, CreateExpandWithFirstLabelGranted) { user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ASSERT_THROW(ExecuteCreateExpand(false, user), QueryRuntimeException); - ASSERT_THROW(ExecuteCreateExpand(true, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(false, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(true, user), QueryRuntimeException); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithSecondLabelGranted) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithSecondLabelGranted) { // Second label granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("Node2", @@ -405,11 +417,11 @@ TEST_F(CreateExpandWithAuthFixture, CreateExpandWithSecondLabelGranted) { user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ASSERT_THROW(ExecuteCreateExpand(false, user), QueryRuntimeException); - ASSERT_THROW(ExecuteCreateExpand(true, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(false, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteCreateExpand(true, user), QueryRuntimeException); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithoutCycleWithEverythingGranted) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithoutCycleWithEverythingGranted) { // All labels granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -417,11 +429,11 @@ TEST_F(CreateExpandWithAuthFixture, CreateExpandWithoutCycleWithEverythingGrante user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteCreateExpand(false, user); - TestCreateExpandHypothesis(2, 1); + this->ExecuteCreateExpand(false, user); + this->TestCreateExpandHypothesis(2, 1); } -TEST_F(CreateExpandWithAuthFixture, CreateExpandWithCycleWithEverythingGranted) { +TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithCycleWithEverythingGranted) { // All labels granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -429,14 +441,13 @@ TEST_F(CreateExpandWithAuthFixture, CreateExpandWithCycleWithEverythingGranted) user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteCreateExpand(true, user); - TestCreateExpandHypothesis(1, 1); + this->ExecuteCreateExpand(true, user); + this->TestCreateExpandHypothesis(1, 1); } -TEST(QueryPlan, MatchCreateNode) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, MatchCreateNode) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add three nodes we'll match and expand-create from dba.InsertVertex(); @@ -445,10 +456,8 @@ TEST(QueryPlan, MatchCreateNode) { dba.AdvanceCommand(); SymbolTable symbol_table; - AstStorage storage; - // first node - auto n_scan_all = MakeScanAll(storage, symbol_table, "n"); + auto n_scan_all = MakeScanAll(this->storage, symbol_table, "n"); // second node NodeCreationInfo m; m.symbol = symbol_table.CreateSymbol("m", true); @@ -456,18 +465,17 @@ TEST(QueryPlan, MatchCreateNode) { auto create_node = std::make_shared<CreateNode>(n_scan_all.op_, m); EXPECT_EQ(CountIterable(dba.Vertices(memgraph::storage::View::OLD)), 3); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*create_node, &context); dba.AdvanceCommand(); EXPECT_EQ(CountIterable(dba.Vertices(memgraph::storage::View::OLD)), 6); } -class MatchCreateNodeWithAuthFixture : public testing::Test { +template <typename StorageType> +class MatchCreateNodeWithAuthFixture : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } @@ -484,7 +492,7 @@ class MatchCreateNodeWithAuthFixture : public testing::Test { } void ExecuteMatchCreate(memgraph::auth::User &user) { - auto n_scan_all = MakeScanAll(storage, symbol_table, "n"); + auto n_scan_all = MakeScanAll(this->storage, symbol_table, "n"); // second node NodeCreationInfo m{}; @@ -494,7 +502,7 @@ class MatchCreateNodeWithAuthFixture : public testing::Test { // creation op auto create_node = std::make_shared<CreateNode>(n_scan_all.op_, m); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*create_node, &context); dba.AdvanceCommand(); @@ -511,25 +519,27 @@ class MatchCreateNodeWithAuthFixture : public testing::Test { } }; -TEST_F(MatchCreateNodeWithAuthFixture, MatchCreateWithAllLabelsDeniedThrows) { +TYPED_TEST_CASE(MatchCreateNodeWithAuthFixture, StorageTypes); + +TYPED_TEST(MatchCreateNodeWithAuthFixture, MatchCreateWithAllLabelsDeniedThrows) { // All labels denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteMatchCreateTestSuite(user, 3), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateTestSuite(user, 3), QueryRuntimeException); } -TEST_F(MatchCreateNodeWithAuthFixture, MatchCreateWithAllLabelsGrantedExecutes) { +TYPED_TEST(MatchCreateNodeWithAuthFixture, MatchCreateWithAllLabelsGrantedExecutes) { // All labels granteddenieddenieddenied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteMatchCreateTestSuite(user, 6); + this->ExecuteMatchCreateTestSuite(user, 6); } -TEST_F(MatchCreateNodeWithAuthFixture, MatchCreateWithOneLabelDeniedThrows) { +TYPED_TEST(MatchCreateNodeWithAuthFixture, MatchCreateWithOneLabelDeniedThrows) { // Label2 denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", @@ -539,14 +549,13 @@ TEST_F(MatchCreateNodeWithAuthFixture, MatchCreateWithOneLabelDeniedThrows) { user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteMatchCreateTestSuite(user, 3), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateTestSuite(user, 3), QueryRuntimeException); } #endif -TEST(QueryPlan, MatchCreateExpand) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, MatchCreateExpand) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add three nodes we'll match and expand-create from dba.InsertVertex(); @@ -558,16 +567,14 @@ TEST(QueryPlan, MatchCreateExpand) { // memgraph::storage::LabelId label_node_2 = dba.NameToLabel("Node2"); // memgraph::storage::PropertyId property = dba.NameToLabel("prop"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); - SymbolTable symbol_table; - AstStorage storage; auto test_create_path = [&](bool cycle, int expected_nodes_created, int expected_edges_created) { int before_v = CountIterable(dba.Vertices(memgraph::storage::View::OLD)); int before_e = CountEdges(&dba, memgraph::storage::View::OLD); // data for the first node - auto n_scan_all = MakeScanAll(storage, symbol_table, "n"); + auto n_scan_all = MakeScanAll(this->storage, symbol_table, "n"); // data for the second node NodeCreationInfo m; @@ -579,7 +586,7 @@ TEST(QueryPlan, MatchCreateExpand) { r.edge_type = edge_type; auto create_expand = std::make_shared<CreateExpand>(m, r, n_scan_all.op_, n_scan_all.sym_, cycle); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*create_expand, &context); dba.AdvanceCommand(); @@ -592,12 +599,11 @@ TEST(QueryPlan, MatchCreateExpand) { } #ifdef MG_ENTERPRISE -class MatchCreateExpandWithAuthFixture : public testing::Test { +template <typename StorageType> +class MatchCreateExpandWithAuthFixture : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } @@ -616,7 +622,7 @@ class MatchCreateExpandWithAuthFixture : public testing::Test { void ExecuteMatchCreateExpand(memgraph::auth::User &user, bool cycle) { // data for the first node - auto n_scan_all = MakeScanAll(storage, symbol_table, "n"); + auto n_scan_all = MakeScanAll(this->storage, symbol_table, "n"); // data for the second node NodeCreationInfo m; @@ -632,7 +638,7 @@ class MatchCreateExpandWithAuthFixture : public testing::Test { auto create_expand = std::make_shared<CreateExpand>(m, r, n_scan_all.op_, n_scan_all.sym_, cycle); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*create_expand, &context); dba.AdvanceCommand(); } @@ -650,36 +656,38 @@ class MatchCreateExpandWithAuthFixture : public testing::Test { } }; -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedEverything) { +TYPED_TEST_CASE(MatchCreateExpandWithAuthFixture, StorageTypes); + +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedEverything) { // All labels denied, All edge types denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedEdgeTypes) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedEdgeTypes) { // All labels granted, All edge types denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedLabels) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedLabels) { // All labels denied, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedOneLabel) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedOneLabel) { // First two label granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::UPDATE); @@ -689,11 +697,11 @@ TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandThrowsWhenDeniedOneLab user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); - ASSERT_THROW(ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(false, 0, 0, user), QueryRuntimeException); + ASSERT_THROW(this->ExecuteMatchCreateExpandTestSuite(true, 0, 0, user), QueryRuntimeException); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWhenGrantedSpecificallyEverything) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWhenGrantedSpecificallyEverything) { // All label granted, Specific edge type granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -701,10 +709,10 @@ TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWh user.fine_grained_access_handler().edge_type_permissions().Grant( "edge_type", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteMatchCreateExpandTestSuite(false, 6, 3, user); + this->ExecuteMatchCreateExpandTestSuite(false, 6, 3, user); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenGrantedSpecificallyEverything) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenGrantedSpecificallyEverything) { // All label granted, Specific edge type granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -712,10 +720,10 @@ TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenG user.fine_grained_access_handler().edge_type_permissions().Grant( "edge_type", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteMatchCreateExpandTestSuite(true, 3, 3, user); + this->ExecuteMatchCreateExpandTestSuite(true, 3, 3, user); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWhenGrantedEverything) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWhenGrantedEverything) { // All labels granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -723,10 +731,10 @@ TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithoutCycleExecutesWh user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteMatchCreateExpandTestSuite(false, 6, 3, user); + this->ExecuteMatchCreateExpandTestSuite(false, 6, 3, user); } -TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenGrantedEverything) { +TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenGrantedEverything) { // All labels granted, All edge types granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", @@ -734,14 +742,13 @@ TEST_F(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesWhenG user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteMatchCreateExpandTestSuite(true, 3, 3, user); + this->ExecuteMatchCreateExpandTestSuite(true, 3, 3, user); } #endif -TEST(QueryPlan, Delete) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, Delete) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // make a fully-connected (one-direction, no cycles) with 4 nodes std::vector<memgraph::query::VertexAccessor> vertices; @@ -754,15 +761,13 @@ TEST(QueryPlan, Delete) { EXPECT_EQ(4, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); EXPECT_EQ(6, CountEdges(&dba, memgraph::storage::View::OLD)); - AstStorage storage; SymbolTable symbol_table; - // attempt to delete a vertex, and fail { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*delete_op, &context), QueryRuntimeException); dba.AdvanceCommand(); EXPECT_EQ(4, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -771,11 +776,11 @@ TEST(QueryPlan, Delete) { // detach delete a single vertex { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, true); Frame frame(symbol_table.max_position()); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); delete_op->MakeCursor(memgraph::utils::NewDeleteResource())->Pull(frame, context); dba.AdvanceCommand(); EXPECT_EQ(3, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -784,12 +789,12 @@ TEST(QueryPlan, Delete) { // delete all remaining edges { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::NEW); - auto r_get = storage.Create<Identifier>("r")->MapTo(r_m.edge_sym_); + auto r_get = this->storage.template Create<Identifier>("r")->MapTo(r_m.edge_sym_); auto delete_op = std::make_shared<plan::Delete>(r_m.op_, std::vector<Expression *>{r_get}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*delete_op, &context); dba.AdvanceCommand(); EXPECT_EQ(3, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -798,10 +803,10 @@ TEST(QueryPlan, Delete) { // delete all remaining vertices { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); PullAll(*delete_op, &context); dba.AdvanceCommand(); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -810,12 +815,11 @@ TEST(QueryPlan, Delete) { } #ifdef MG_ENTERPRISE -class DeleteOperatorWithAuthFixture : public testing::Test { +template <typename StorageType> +class DeleteOperatorWithAuthFixture : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } @@ -844,11 +848,11 @@ class DeleteOperatorWithAuthFixture : public testing::Test { }; void DeleteAllNodes(memgraph::auth::User &user) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); Frame frame(symbol_table.max_position()); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, true); PullAll(*delete_op, &context); dba.AdvanceCommand(); @@ -866,13 +870,13 @@ class DeleteOperatorWithAuthFixture : public testing::Test { }; void DeleteAllEdges(memgraph::auth::User &user) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::NEW); - auto r_get = storage.Create<Identifier>("r")->MapTo(r_m.edge_sym_); + auto r_get = this->storage.template Create<Identifier>("r")->MapTo(r_m.edge_sym_); auto delete_op = std::make_shared<plan::Delete>(r_m.op_, std::vector<Expression *>{r_get}, false); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*delete_op, &context); dba.AdvanceCommand(); }; @@ -891,39 +895,41 @@ class DeleteOperatorWithAuthFixture : public testing::Test { } }; -TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenAllLabelsDenied) { +TYPED_TEST_CASE(DeleteOperatorWithAuthFixture, StorageTypes); + +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenAllLabelsDenied) { // All labels denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); + ASSERT_THROW(this->ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); } -TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenPartialLabelsGranted) { +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenPartialLabelsGranted) { // One Label granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - ASSERT_THROW(ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); + ASSERT_THROW(this->ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); } -TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeExecutesWhenGrantedAllLabels) { +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteNodeExecutesWhenGrantedAllLabels) { // All labels granted memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - ExecuteDeleteNodesTestSuite(user, 0); + this->ExecuteDeleteNodesTestSuite(user, 0); } -TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenEdgeTypesNotGranted) { +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteNodeThrowsExceptionWhenEdgeTypesNotGranted) { // All labels granted,All edge types denied memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); + ASSERT_THROW(this->ExecuteDeleteNodesTestSuite(user, 0), QueryRuntimeException); } -TEST_F(DeleteOperatorWithAuthFixture, DeleteEdgesThrowsErrorWhenPartialGrant) { +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteEdgesThrowsErrorWhenPartialGrant) { // Specific label granted, Specific edge types granted memgraph::auth::User user{"test"}; @@ -940,10 +946,10 @@ TEST_F(DeleteOperatorWithAuthFixture, DeleteEdgesThrowsErrorWhenPartialGrant) { user.fine_grained_access_handler().edge_type_permissions().Grant("type3", memgraph::auth::FineGrainedPermission::UPDATE); - ASSERT_THROW(ExecuteDeleteEdgesTestSuite(user, 0), QueryRuntimeException); + ASSERT_THROW(this->ExecuteDeleteEdgesTestSuite(user, 0), QueryRuntimeException); } -TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeAndDeleteEdgePerformWhenGranted) { +TYPED_TEST(DeleteOperatorWithAuthFixture, DeleteNodeAndDeleteEdgePerformWhenGranted) { // All labels granted, All edge_types granted memgraph::auth::User user{"test"}; @@ -952,14 +958,14 @@ TEST_F(DeleteOperatorWithAuthFixture, DeleteNodeAndDeleteEdgePerformWhenGranted) user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - InitGraph(); - DeleteAllNodes(user); - TestDeleteNodesHypothesis(0); - TestDeleteEdgesHypothesis(0); + this->InitGraph(); + this->DeleteAllNodes(user); + this->TestDeleteNodesHypothesis(0); + this->TestDeleteEdgesHypothesis(0); } #endif -TEST(QueryPlan, DeleteTwiceDeleteBlockingEdge) { +TYPED_TEST(QueryPlanTest, DeleteTwiceDeleteBlockingEdge) { // test deleting the same vertex and edge multiple times // // also test vertex deletion succeeds if the prohibiting @@ -971,10 +977,9 @@ TEST(QueryPlan, DeleteTwiceDeleteBlockingEdge) { // CREATE ()-[:T]->() // MATCH (n)-[r]-(m) [DETACH] DELETE n, r, m - auto test_delete = [](bool detach) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto test_delete = [this](bool detach) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -983,20 +988,19 @@ TEST(QueryPlan, DeleteTwiceDeleteBlockingEdge) { EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); EXPECT_EQ(1, CountEdges(&dba, memgraph::storage::View::OLD)); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", false, memgraph::storage::View::OLD); // getter expressions for deletion - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); - auto r_get = storage.Create<Identifier>("r")->MapTo(r_m.edge_sym_); - auto m_get = storage.Create<Identifier>("m")->MapTo(r_m.node_sym_); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); + auto r_get = this->storage.template Create<Identifier>("r")->MapTo(r_m.edge_sym_); + auto m_get = this->storage.template Create<Identifier>("m")->MapTo(r_m.node_sym_); auto delete_op = std::make_shared<plan::Delete>(r_m.op_, std::vector<Expression *>{n_get, r_get, m_get}, detach); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*delete_op, &context)); dba.AdvanceCommand(); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -1007,13 +1011,12 @@ TEST(QueryPlan, DeleteTwiceDeleteBlockingEdge) { test_delete(false); } -TEST(QueryPlan, DeleteReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // make a fully-connected (one-direction, no cycles) with 4 nodes - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); for (int i = 0; i < 4; ++i) { auto va = dba.InsertVertex(); ASSERT_TRUE(va.SetProperty(prop.second, memgraph::storage::PropertyValue(42)).HasValue()); @@ -1023,77 +1026,71 @@ TEST(QueryPlan, DeleteReturn) { EXPECT_EQ(4, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); EXPECT_EQ(0, CountEdges(&dba, memgraph::storage::View::OLD)); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, true); - auto prop_lookup = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); - auto n_p = storage.Create<NamedExpression>("n", prop_lookup)->MapTo(symbol_table.CreateSymbol("bla", true)); + auto prop_lookup = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); + auto n_p = + this->storage.template Create<NamedExpression>("n", prop_lookup)->MapTo(symbol_table.CreateSymbol("bla", true)); auto produce = MakeProduce(delete_op, n_p); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); ASSERT_THROW(CollectProduce(*produce, &context), QueryRuntimeException); } -TEST(QueryPlan, DeleteNull) { +TYPED_TEST(QueryPlanTest, DeleteNull) { // test (simplified) WITH Null as x delete x - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto once = std::make_shared<Once>(); auto delete_op = std::make_shared<plan::Delete>(once, std::vector<Expression *>{LITERAL(TypedValue())}, false); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*delete_op, &context)); } -TEST(QueryPlan, DeleteAdvance) { +TYPED_TEST(QueryPlanTest, DeleteAdvance) { // test queries on empty DB: // CREATE (n) // MATCH (n) DELETE n WITH n ... // this fails only if the deleted record `n` is actually used in subsequent // clauses, which is compatible with Neo's behavior. - memgraph::storage::Storage db; - - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); auto advance = std::make_shared<Accumulate>(delete_op, std::vector<Symbol>{n.sym_}, true); auto res_sym = symbol_table.CreateSymbol("res", true); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.AdvanceCommand(); auto produce = MakeProduce(advance, NEXPR("res", LITERAL(42))->MapTo(res_sym)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*produce, &context)); } { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.AdvanceCommand(); - auto n_prop = PROPERTY_LOOKUP(n_get, dba.NameToProperty("prop")); + auto n_prop = PROPERTY_LOOKUP(dba, n_get, dba.NameToProperty("prop")); auto produce = MakeProduce(advance, NEXPR("res", n_prop)->MapTo(res_sym)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*produce, &context), QueryRuntimeException); } } -TEST(QueryPlan, SetProperty) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, SetProperty) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // graph with 4 vertices in connected pairs // the origin vertex in each par and both edges @@ -1107,29 +1104,29 @@ TEST(QueryPlan, SetProperty) { ASSERT_TRUE(dba.InsertEdge(&v2, &v4, edge_type).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // scan (n)-[r]->(m) - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); // set prop1 to 42 on n and r auto prop1 = dba.NameToProperty("prop1"); auto literal = LITERAL(42); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop1); auto set_n_p = std::make_shared<plan::SetProperty>(r_m.op_, prop1, n_p, literal); - auto r_p = PROPERTY_LOOKUP(IDENT("r")->MapTo(r_m.edge_sym_), prop1); + auto r_p = PROPERTY_LOOKUP(dba, IDENT("r")->MapTo(r_m.edge_sym_), prop1); auto set_r_p = std::make_shared<plan::SetProperty>(set_n_p, prop1, r_p, literal); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*set_r_p, &context)); dba.AdvanceCommand(); EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 2); for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + dba.PrefetchOutEdges(vertex); auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(maybe_edges.HasValue()); for (auto edge : *maybe_edges) { @@ -1147,11 +1144,10 @@ TEST(QueryPlan, SetProperty) { } } -TEST(QueryPlan, SetProperties) { - auto test_set_properties = [](bool update) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, SetProperties) { + auto test_set_properties = [this](bool update) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // graph: ({a: 0})-[:R {b:1}]->({c:2}) auto prop_a = dba.NameToProperty("a"); @@ -1165,12 +1161,11 @@ TEST(QueryPlan, SetProperties) { ASSERT_TRUE(v2.SetProperty(prop_c, memgraph::storage::PropertyValue(2)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // scan (n)-[r]->(m) - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); auto op = update ? plan::SetProperties::Op::UPDATE : plan::SetProperties::Op::REPLACE; @@ -1180,12 +1175,13 @@ TEST(QueryPlan, SetProperties) { auto m_ident = IDENT("m")->MapTo(r_m.node_sym_); auto set_r_to_n = std::make_shared<plan::SetProperties>(r_m.op_, n.sym_, r_ident, op); auto set_m_to_r = std::make_shared<plan::SetProperties>(set_r_to_n, r_m.edge_sym_, m_ident, op); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*set_m_to_r, &context)); dba.AdvanceCommand(); EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 1); for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + dba.PrefetchOutEdges(vertex); auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(maybe_edges.HasValue()); for (auto edge : *maybe_edges) { @@ -1223,10 +1219,9 @@ TEST(QueryPlan, SetProperties) { test_set_properties(false); } -TEST(QueryPlan, SetLabels) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, SetLabels) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1235,13 +1230,12 @@ TEST(QueryPlan, SetLabels) { ASSERT_TRUE(dba.InsertVertex().AddLabel(label1).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto label_set = std::make_shared<plan::SetLabels>(n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{label2, label3}); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*label_set, &context)); for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { @@ -1252,7 +1246,7 @@ TEST(QueryPlan, SetLabels) { } #ifdef MG_ENTERPRISE -TEST(QueryPlan, SetLabelsWithFineGrained) { +TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) { memgraph::license::global_license_checker.EnableTesting(); auto set_labels = [&](memgraph::auth::User user, memgraph::query::DbAccessor dba, std::vector<memgraph::storage::LabelId> labels) { @@ -1260,14 +1254,13 @@ TEST(QueryPlan, SetLabelsWithFineGrained) { ASSERT_TRUE(dba.InsertVertex().AddLabel(labels[0]).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto label_set = std::make_shared<plan::SetLabels>(n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{labels[1], labels[2]}); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*label_set, &context); }; @@ -1277,9 +1270,8 @@ TEST(QueryPlan, SetLabelsWithFineGrained) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1295,9 +1287,8 @@ TEST(QueryPlan, SetLabelsWithFineGrained) { { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1315,9 +1306,8 @@ TEST(QueryPlan, SetLabelsWithFineGrained) { user.fine_grained_access_handler().label_permissions().Grant("label3", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1327,10 +1317,9 @@ TEST(QueryPlan, SetLabelsWithFineGrained) { } #endif -TEST(QueryPlan, RemoveProperty) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, RemoveProperty) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // graph with 4 vertices in connected pairs // the origin vertex in each par and both edges @@ -1355,25 +1344,25 @@ TEST(QueryPlan, RemoveProperty) { ASSERT_TRUE(v2.SetProperty(prop2, memgraph::storage::PropertyValue(0)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // scan (n)-[r]->(m) - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop1); auto set_n_p = std::make_shared<plan::RemoveProperty>(r_m.op_, prop1, n_p); - auto r_p = PROPERTY_LOOKUP(IDENT("r")->MapTo(r_m.edge_sym_), prop1); + auto r_p = PROPERTY_LOOKUP(dba, IDENT("r")->MapTo(r_m.edge_sym_), prop1); auto set_r_p = std::make_shared<plan::RemoveProperty>(set_n_p, prop1, r_p); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*set_r_p, &context)); dba.AdvanceCommand(); EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 2); for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + dba.PrefetchOutEdges(vertex); auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(maybe_edges.HasValue()); for (auto edge : *maybe_edges) { @@ -1391,10 +1380,9 @@ TEST(QueryPlan, RemoveProperty) { } } -TEST(QueryPlan, RemoveLabels) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, RemoveLabels) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1408,13 +1396,12 @@ TEST(QueryPlan, RemoveLabels) { ASSERT_TRUE(v2.AddLabel(label3).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto label_remove = std::make_shared<plan::RemoveLabels>(n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{label1, label2}); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*label_remove, &context)); for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { @@ -1425,7 +1412,7 @@ TEST(QueryPlan, RemoveLabels) { } #ifdef MG_ENTERPRISE -TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { +TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) { memgraph::license::global_license_checker.EnableTesting(); auto remove_labels = [&](memgraph::auth::User user, memgraph::query::DbAccessor dba, std::vector<memgraph::storage::LabelId> labels) { @@ -1438,15 +1425,14 @@ TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { ASSERT_TRUE(v2.AddLabel(labels[2]).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto label_remove = std::make_shared<plan::RemoveLabels>( n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{labels[0], labels[1]}); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*label_remove, &context); }; @@ -1456,9 +1442,8 @@ TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1474,9 +1459,8 @@ TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1494,9 +1478,8 @@ TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("label3", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); auto label3 = dba.NameToLabel("label3"); @@ -1506,13 +1489,12 @@ TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { } #endif -TEST(QueryPlan, NodeFilterSet) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, NodeFilterSet) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3. auto v1 = dba.InsertVertex(); - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); ASSERT_TRUE(v1.SetProperty(prop.second, memgraph::storage::PropertyValue(42)).HasValue()); auto v2 = dba.InsertVertex(); auto v3 = dba.InsertVertex(); @@ -1523,21 +1505,21 @@ TEST(QueryPlan, NodeFilterSet) { // Create operations which match (v1 {prop: 42}) -- (v) and increment the // v1.prop. The expected result is two incremenentations, since v1 is matched // twice for 2 edges it has. - AstStorage storage; SymbolTable symbol_table; // MATCH (n {prop: 42}) -[r]- (m) - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - std::get<0>(scan_all.node_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); - auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", - false, memgraph::storage::View::OLD); - auto *filter_expr = - EQ(storage.Create<PropertyLookup>(scan_all.node_->identifier_, storage.GetPropertyIx(prop.first)), LITERAL(42)); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + std::get<0>(scan_all.node_->properties_)[this->storage.GetPropertyIx(prop.first)] = LITERAL(42); + auto expand = MakeExpand(this->storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, + "m", false, memgraph::storage::View::OLD); + auto *filter_expr = EQ(this->storage.template Create<PropertyLookup>(scan_all.node_->identifier_, + this->storage.GetPropertyIx(prop.first)), + LITERAL(42)); auto node_filter = std::make_shared<Filter>(expand.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); // SET n.prop = n.prop + 1 - auto set_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); + auto set_prop = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), prop); auto add = ADD(set_prop, LITERAL(1)); auto set = std::make_shared<plan::SetProperty>(node_filter, prop.second, set_prop, add); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*set, &context)); dba.AdvanceCommand(); auto prop_eq = TypedValue(*v1.GetProperty(memgraph::storage::View::OLD, prop.second)) == TypedValue(42 + 2); @@ -1545,13 +1527,12 @@ TEST(QueryPlan, NodeFilterSet) { EXPECT_TRUE(prop_eq.ValueBool()); } -TEST(QueryPlan, FilterRemove) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, FilterRemove) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3. auto v1 = dba.InsertVertex(); - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); ASSERT_TRUE(v1.SetProperty(prop.second, memgraph::storage::PropertyValue(42)).HasValue()); auto v2 = dba.InsertVertex(); auto v3 = dba.InsertVertex(); @@ -1561,85 +1542,80 @@ TEST(QueryPlan, FilterRemove) { dba.AdvanceCommand(); // Create operations which match (v1 {prop: 42}) -- (v) and remove v1.prop. // The expected result is two matches, for each edge of v1. - AstStorage storage; SymbolTable symbol_table; // MATCH (n) -[r]- (m) WHERE n.prop < 43 - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - std::get<0>(scan_all.node_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); - auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", - false, memgraph::storage::View::OLD); - auto filter_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + std::get<0>(scan_all.node_->properties_)[this->storage.GetPropertyIx(prop.first)] = LITERAL(42); + auto expand = MakeExpand(this->storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::BOTH, {}, + "m", false, memgraph::storage::View::OLD); + auto filter_prop = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), prop); auto filter = std::make_shared<Filter>(expand.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, LESS(filter_prop, LITERAL(43))); // REMOVE n.prop - auto rem_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), prop); + auto rem_prop = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), prop); auto rem = std::make_shared<plan::RemoveProperty>(filter, prop.second, rem_prop); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(2, PullAll(*rem, &context)); dba.AdvanceCommand(); EXPECT_EQ(v1.GetProperty(memgraph::storage::View::OLD, prop.second)->type(), memgraph::storage::PropertyValue::Type::Null); } -TEST(QueryPlan, SetRemove) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, SetRemove) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); dba.AdvanceCommand(); // Create operations which match (v) and set and remove v :label. // The expected result is single (v) as it was at the start. - AstStorage storage; SymbolTable symbol_table; // MATCH (n) SET n :label1 :label2 REMOVE n :label1 :label2 - auto scan_all = MakeScanAll(storage, symbol_table, "n"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); auto set = std::make_shared<plan::SetLabels>(scan_all.op_, scan_all.sym_, std::vector<memgraph::storage::LabelId>{label1, label2}); auto rem = std::make_shared<plan::RemoveLabels>(set, scan_all.sym_, std::vector<memgraph::storage::LabelId>{label1, label2}); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*rem, &context)); dba.AdvanceCommand(); EXPECT_FALSE(*v.HasLabel(memgraph::storage::View::OLD, label1)); EXPECT_FALSE(*v.HasLabel(memgraph::storage::View::OLD, label2)); } -TEST(QueryPlan, Merge) { +TYPED_TEST(QueryPlanTest, Merge) { // test setup: // - three nodes, two of them connected with T // - merge input branch matches all nodes // - merge_match branch looks for an expansion (any direction) // and sets some property (for result validation) // - merge_create branch just sets some other property - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); ASSERT_TRUE(dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("Type")).HasValue()); auto v3 = dba.InsertVertex(); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto prop = PROPERTY_PAIR("property"); - auto n = MakeScanAll(storage, symbol_table, "n"); + auto prop = PROPERTY_PAIR(dba, "property"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); // merge_match branch - auto r_m = MakeExpand(storage, symbol_table, std::make_shared<Once>(), n.sym_, "r", EdgeAtom::Direction::BOTH, {}, - "m", false, memgraph::storage::View::OLD); - auto m_p = PROPERTY_LOOKUP(IDENT("m")->MapTo(r_m.node_sym_), prop); + auto r_m = MakeExpand(this->storage, symbol_table, std::make_shared<Once>(), n.sym_, "r", EdgeAtom::Direction::BOTH, + {}, "m", false, memgraph::storage::View::OLD); + auto m_p = PROPERTY_LOOKUP(dba, IDENT("m")->MapTo(r_m.node_sym_), prop); auto m_set = std::make_shared<plan::SetProperty>(r_m.op_, prop.second, m_p, LITERAL(1)); // merge_create branch - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto n_set = std::make_shared<plan::SetProperty>(std::make_shared<Once>(), prop.second, n_p, LITERAL(2)); auto merge = std::make_shared<plan::Merge>(n.op_, m_set, n_set); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); ASSERT_EQ(3, PullAll(*merge, &context)); dba.AdvanceCommand(); @@ -1654,13 +1630,11 @@ TEST(QueryPlan, Merge) { ASSERT_EQ(v3.GetProperty(memgraph::storage::View::OLD, prop.second)->ValueInt(), 2); } -TEST(QueryPlan, MergeNoInput) { +TYPED_TEST(QueryPlanTest, MergeNoInput) { // merge with no input, creates a single node - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; NodeCreationInfo node; @@ -1669,144 +1643,129 @@ TEST(QueryPlan, MergeNoInput) { auto merge = std::make_shared<plan::Merge>(nullptr, create, create); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*merge, &context)); dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); } -TEST(QueryPlan, SetPropertyOnNull) { +TYPED_TEST(QueryPlanTest, SetPropertyOnNull) { // SET (Null).prop = 42 - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); auto null = LITERAL(TypedValue()); auto literal = LITERAL(42); - auto n_prop = PROPERTY_LOOKUP(null, prop); + auto n_prop = PROPERTY_LOOKUP(dba, null, prop); auto once = std::make_shared<Once>(); auto set_op = std::make_shared<plan::SetProperty>(once, prop.second, n_prop, literal); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*set_op, &context)); } -TEST(QueryPlan, SetPropertiesOnNull) { +TYPED_TEST(QueryPlanTest, SetPropertiesOnNull) { // OPTIONAL MATCH (n) SET n = n - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto n_ident = IDENT("n")->MapTo(n.sym_); auto optional = std::make_shared<plan::Optional>(nullptr, n.op_, std::vector<Symbol>{n.sym_}); auto set_op = std::make_shared<plan::SetProperties>(optional, n.sym_, n_ident, plan::SetProperties::Op::REPLACE); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*set_op, &context)); } -TEST(QueryPlan, SetLabelsOnNull) { +TYPED_TEST(QueryPlanTest, SetLabelsOnNull) { // OPTIONAL MATCH (n) SET n :label - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label = dba.NameToLabel("label"); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto optional = std::make_shared<plan::Optional>(nullptr, n.op_, std::vector<Symbol>{n.sym_}); auto set_op = std::make_shared<plan::SetLabels>(optional, n.sym_, std::vector<memgraph::storage::LabelId>{label}); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*set_op, &context)); } -TEST(QueryPlan, RemovePropertyOnNull) { +TYPED_TEST(QueryPlanTest, RemovePropertyOnNull) { // REMOVE (Null).prop - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); auto null = LITERAL(TypedValue()); - auto n_prop = PROPERTY_LOOKUP(null, prop); + auto n_prop = PROPERTY_LOOKUP(dba, null, prop); auto once = std::make_shared<Once>(); auto remove_op = std::make_shared<plan::RemoveProperty>(once, prop.second, n_prop); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*remove_op, &context)); } -TEST(QueryPlan, RemoveLabelsOnNull) { +TYPED_TEST(QueryPlanTest, RemoveLabelsOnNull) { // OPTIONAL MATCH (n) REMOVE n :label - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto label = dba.NameToLabel("label"); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto optional = std::make_shared<plan::Optional>(nullptr, n.op_, std::vector<Symbol>{n.sym_}); auto remove_op = std::make_shared<plan::RemoveLabels>(optional, n.sym_, std::vector<memgraph::storage::LabelId>{label}); EXPECT_EQ(0, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*remove_op, &context)); } -TEST(QueryPlan, DeleteSetProperty) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteSetProperty) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // MATCH (n) DELETE n SET n.property = 42 - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); - auto prop = PROPERTY_PAIR("property"); - auto n_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto prop = PROPERTY_PAIR(dba, "property"); + auto n_prop = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto set_op = std::make_shared<plan::SetProperty>(delete_op, prop.second, n_prop, LITERAL(42)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*set_op, &context), QueryRuntimeException); } -TEST(QueryPlan, DeleteSetPropertiesFromMap) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFromMap) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // MATCH (n) DELETE n SET n = {property: 42} - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); std::unordered_map<PropertyIx, Expression *> prop_map; - prop_map.emplace(storage.GetPropertyIx(prop.first), LITERAL(42)); - auto *rhs = storage.Create<MapLiteral>(prop_map); + prop_map.emplace(this->storage.GetPropertyIx(prop.first), LITERAL(42)); + auto *rhs = this->storage.template Create<MapLiteral>(prop_map); for (auto op_type : {plan::SetProperties::Op::REPLACE, plan::SetProperties::Op::UPDATE}) { auto set_op = std::make_shared<plan::SetProperties>(delete_op, n.sym_, rhs, op_type); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*set_op, &context), QueryRuntimeException); } } -TEST(QueryPlan, DeleteSetPropertiesFrom) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFrom) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. { auto v = dba.InsertVertex(); @@ -1814,58 +1773,53 @@ TEST(QueryPlan, DeleteSetPropertiesFrom) { } dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // MATCH (n) DELETE n SET n = n - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); auto *rhs = IDENT("n")->MapTo(n.sym_); for (auto op_type : {plan::SetProperties::Op::REPLACE, plan::SetProperties::Op::UPDATE}) { auto set_op = std::make_shared<plan::SetProperties>(delete_op, n.sym_, rhs, op_type); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*set_op, &context), QueryRuntimeException); } } -TEST(QueryPlan, DeleteRemoveLabels) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteRemoveLabels) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // MATCH (n) DELETE n REMOVE n :label - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); std::vector<memgraph::storage::LabelId> labels{dba.NameToLabel("label")}; auto rem_op = std::make_shared<plan::RemoveLabels>(delete_op, n.sym_, labels); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*rem_op, &context), QueryRuntimeException); } -TEST(QueryPlan, DeleteRemoveProperty) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlanTest, DeleteRemoveProperty) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); dba.AdvanceCommand(); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // MATCH (n) DELETE n REMOVE n.property - auto n = MakeScanAll(storage, symbol_table, "n"); - auto n_get = storage.Create<Identifier>("n")->MapTo(n.sym_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto n_get = this->storage.template Create<Identifier>("n")->MapTo(n.sym_); auto delete_op = std::make_shared<plan::Delete>(n.op_, std::vector<Expression *>{n_get}, false); - auto prop = PROPERTY_PAIR("property"); - auto n_prop = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop); + auto prop = PROPERTY_PAIR(dba, "property"); + auto n_prop = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), prop); auto rem_op = std::make_shared<plan::RemoveProperty>(delete_op, prop.second, n_prop); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*rem_op, &context), QueryRuntimeException); } @@ -1873,12 +1827,11 @@ TEST(QueryPlan, DeleteRemoveProperty) { //// FINE GRAINED AUTHORIZATION ///// ////////////////////////////////////////////// #ifdef MG_ENTERPRISE -class UpdatePropertiesWithAuthFixture : public testing::Test { +template <typename StorageType> +class UpdatePropertiesWithAuthFixture : public QueryPlanTest<StorageType> { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; const std::string vertex_label_name = "l1"; @@ -1909,10 +1862,10 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteSetPropertyOnVertex(memgraph::auth::User user, int new_property_value) { // MATCH (n) SET n.prop = 2 - auto scan_all = MakeScanAll(storage, symbol_table, "n"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); auto literal = LITERAL(new_property_value); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), entity_prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), entity_prop); auto set_property = std::make_shared<plan::SetProperty>(scan_all.op_, entity_prop, n_p, literal); // produce the node @@ -1921,7 +1874,7 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { auto produce = MakeProduce(set_property, output); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*produce, &context); dba.AdvanceCommand(); @@ -1929,16 +1882,16 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteSetPropertyOnEdge(memgraph::auth::User user, int new_property_value) { // MATCH (n)-[r]->(m) SET r.prop = 2 - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", - false, memgraph::storage::View::OLD); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + auto expand = MakeExpand(this->storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, + {}, "m", false, memgraph::storage::View::OLD); // set property to 2 on n auto literal = LITERAL(new_property_value); - auto n_p = PROPERTY_LOOKUP(IDENT("r")->MapTo(expand.edge_sym_), entity_prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("r")->MapTo(expand.edge_sym_), entity_prop); auto set_property = std::make_shared<plan::SetProperty>(expand.op_, entity_prop, n_p, literal); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*set_property, &context); dba.AdvanceCommand(); @@ -1946,12 +1899,12 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteSetPropertiesOnVertex(memgraph::auth::User user, int new_property_value) { // MATCH (n) SET n = {prop: 2}; - auto scan_all = MakeScanAll(storage, symbol_table, "n"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); - auto prop = PROPERTY_PAIR(entity_prop_name); + auto prop = PROPERTY_PAIR(dba, entity_prop_name); std::unordered_map<PropertyIx, Expression *> prop_map; - prop_map.emplace(storage.GetPropertyIx(prop.first), LITERAL(new_property_value)); - auto *rhs = storage.Create<MapLiteral>(prop_map); + prop_map.emplace(this->storage.GetPropertyIx(prop.first), LITERAL(new_property_value)); + auto *rhs = this->storage.template Create<MapLiteral>(prop_map); auto set_properties = std::make_shared<plan::SetProperties>(scan_all.op_, scan_all.sym_, rhs, plan::SetProperties::Op::UPDATE); @@ -1960,7 +1913,7 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { auto produce = MakeProduce(set_properties, output); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*produce, &context); dba.AdvanceCommand(); @@ -1968,14 +1921,14 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteSetPropertiesOnEdge(memgraph::auth::User user, int new_property_value) { // MATCH (n)-[r]->(m) SET r = {prop: 2}; - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", - false, memgraph::storage::View::OLD); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + auto expand = MakeExpand(this->storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, + {}, "m", false, memgraph::storage::View::OLD); - auto prop = PROPERTY_PAIR(entity_prop_name); + auto prop = PROPERTY_PAIR(dba, entity_prop_name); std::unordered_map<PropertyIx, Expression *> prop_map; - prop_map.emplace(storage.GetPropertyIx(prop.first), LITERAL(new_property_value)); - auto *rhs = storage.Create<MapLiteral>(prop_map); + prop_map.emplace(this->storage.GetPropertyIx(prop.first), LITERAL(new_property_value)); + auto *rhs = this->storage.template Create<MapLiteral>(prop_map); auto set_properties = std::make_shared<plan::SetProperties>(expand.op_, expand.edge_sym_, rhs, plan::SetProperties::Op::UPDATE); @@ -1984,7 +1937,7 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { auto produce = MakeProduce(set_properties, output); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*produce, &context); dba.AdvanceCommand(); @@ -1992,9 +1945,9 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteRemovePropertyOnVertex(memgraph::auth::User user) { // MATCH (n) REMOVE n.prop - auto scan_all = MakeScanAll(storage, symbol_table, "n"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), entity_prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), entity_prop); auto remove_property = std::make_shared<plan::RemoveProperty>(scan_all.op_, entity_prop, n_p); // produce the node @@ -2003,7 +1956,7 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { auto produce = MakeProduce(remove_property, output); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*produce, &context); dba.AdvanceCommand(); @@ -2011,43 +1964,45 @@ class UpdatePropertiesWithAuthFixture : public testing::Test { void ExecuteRemovePropertyOnEdge(memgraph::auth::User user) { // MATCH (n)-[r]->(m) REMOVE r.prop - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - auto expand = MakeExpand(storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", - false, memgraph::storage::View::OLD); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + auto expand = MakeExpand(this->storage, symbol_table, scan_all.op_, scan_all.sym_, "r", EdgeAtom::Direction::OUT, + {}, "m", false, memgraph::storage::View::OLD); // set property to 2 on n - auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(expand.edge_sym_), entity_prop); + auto n_p = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(expand.edge_sym_), entity_prop); auto remove_property = std::make_shared<plan::RemoveProperty>(expand.op_, entity_prop, n_p); memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + auto context = MakeContextWithFineGrainedChecker(this->storage, symbol_table, &dba, &auth_checker); PullAll(*remove_property, &context); dba.AdvanceCommand(); }; }; -TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyWithAuthChecker) { - // Add a single vertex - auto v = dba.InsertVertex(); - ASSERT_TRUE(v.AddLabel(vertex_label).HasValue()); - ASSERT_TRUE(v.SetProperty(entity_prop, entity_prop_value).HasValue()); - dba.AdvanceCommand(); +TYPED_TEST_CASE(UpdatePropertiesWithAuthFixture, StorageTypes); - EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); +TYPED_TEST(UpdatePropertiesWithAuthFixture, SetPropertyWithAuthChecker) { + // Add a single vertex + auto v = this->dba.InsertVertex(); + ASSERT_TRUE(v.AddLabel(this->vertex_label).HasValue()); + ASSERT_TRUE(v.SetProperty(this->entity_prop, this->entity_prop_value).HasValue()); + this->dba.AdvanceCommand(); + + EXPECT_EQ(1, CountIterable(this->dba.Vertices(memgraph::storage::View::OLD))); auto test_hypothesis = [&](int expected_property_value) { - auto vertex = *dba.Vertices(memgraph::storage::View::NEW).begin(); + auto vertex = *this->dba.Vertices(memgraph::storage::View::NEW).begin(); auto maybe_properties = vertex.Properties(memgraph::storage::View::NEW); ASSERT_TRUE(maybe_properties.HasValue()); const auto &properties = *maybe_properties; EXPECT_EQ(properties.size(), 1); - auto maybe_prop = vertex.GetProperty(memgraph::storage::View::NEW, entity_prop); + auto maybe_prop = vertex.GetProperty(memgraph::storage::View::NEW, this->entity_prop); ASSERT_TRUE(maybe_prop.HasValue()); ASSERT_EQ(maybe_prop->ValueInt(), expected_property_value); }; auto test_remove_hypothesis = [&](int properties_size) { - auto vertex = *dba.Vertices(memgraph::storage::View::NEW).begin(); + auto vertex = *this->dba.Vertices(memgraph::storage::View::NEW).begin(); auto maybe_properties = vertex.Properties(memgraph::storage::View::NEW); ASSERT_TRUE(maybe_properties.HasValue()); const auto &properties = *maybe_properties; @@ -2059,35 +2014,35 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyWithAuthChecker) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(1); } { auto user = memgraph::auth::User{"denied_label"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::NOTHING); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(1); } @@ -2096,54 +2051,54 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyWithAuthChecker) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(0); } { auto user = memgraph::auth::User{"granted_label"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::UPDATE); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(0); } { auto user = memgraph::auth::User{"granted_read_label"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::READ); - SetVertexProperty(v); - ASSERT_THROW(ExecuteSetPropertyOnVertex(user, 2), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteSetPropertyOnVertex(user, 2), QueryRuntimeException); test_hypothesis(1); - SetVertexProperty(v); - ASSERT_THROW(ExecuteSetPropertiesOnVertex(user, 2), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteSetPropertiesOnVertex(user, 2), QueryRuntimeException); test_hypothesis(1); - SetVertexProperty(v); - ASSERT_THROW(ExecuteRemovePropertyOnVertex(user), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteRemovePropertyOnVertex(user), QueryRuntimeException); test_remove_hypothesis(1); } @@ -2152,120 +2107,120 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyWithAuthChecker) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - SetVertexProperty(v); - ASSERT_THROW(ExecuteSetPropertyOnVertex(user, 2), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteSetPropertyOnVertex(user, 2), QueryRuntimeException); test_hypothesis(1); - SetVertexProperty(v); - ASSERT_THROW(ExecuteSetPropertiesOnVertex(user, 2), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteSetPropertiesOnVertex(user, 2), QueryRuntimeException); test_hypothesis(1); - SetVertexProperty(v); - ASSERT_THROW(ExecuteRemovePropertyOnVertex(user), QueryRuntimeException); + this->SetVertexProperty(v); + ASSERT_THROW(this->ExecuteRemovePropertyOnVertex(user), QueryRuntimeException); test_remove_hypothesis(1); } { auto user = memgraph::auth::User{"granted_update_label_denied_read_global"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::UPDATE); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(0); } { auto user = memgraph::auth::User{"granted_update_global_denied_read_label"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(1); } { auto user = memgraph::auth::User{"granted_create_delete_label_denied_read_global"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::CREATE_DELETE); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(2); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(0); } { auto user = memgraph::auth::User{"granted_create_delete_global_denied_read_label"}; - user.fine_grained_access_handler().label_permissions().Grant(vertex_label_name, + user.fine_grained_access_handler().label_permissions().Grant(this->vertex_label_name, memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - SetVertexProperty(v); - ExecuteSetPropertyOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertyOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteSetPropertiesOnVertex(user, 2); + this->SetVertexProperty(v); + this->ExecuteSetPropertiesOnVertex(user, 2); test_hypothesis(1); - SetVertexProperty(v); - ExecuteRemovePropertyOnVertex(user); + this->SetVertexProperty(v); + this->ExecuteRemovePropertyOnVertex(user); test_remove_hypothesis(1); } } -TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { +TYPED_TEST(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { // Add a single vertex - auto v1 = dba.InsertVertex(); - ASSERT_TRUE(v1.AddLabel(label_1).HasValue()); + auto v1 = this->dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(this->label_1).HasValue()); - auto v2 = dba.InsertVertex(); - ASSERT_TRUE(v2.AddLabel(label_2).HasValue()); + auto v2 = this->dba.InsertVertex(); + ASSERT_TRUE(v2.AddLabel(this->label_2).HasValue()); auto edge_type_name = "edge_type"; - auto edge_type_id = dba.NameToEdgeType(edge_type_name); - auto edge = dba.InsertEdge(&v1, &v2, edge_type_id); + auto edge_type_id = this->dba.NameToEdgeType(edge_type_name); + auto edge = this->dba.InsertEdge(&v1, &v2, edge_type_id); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(entity_prop, entity_prop_value).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge->SetProperty(this->entity_prop, this->entity_prop_value).HasValue()); + this->dba.AdvanceCommand(); - EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); + EXPECT_EQ(2, CountIterable(this->dba.Vertices(memgraph::storage::View::OLD))); auto test_hypothesis = [&](int expected_property_value) { - for (auto vertex : dba.Vertices(memgraph::storage::View::NEW)) { + for (auto vertex : this->dba.Vertices(memgraph::storage::View::NEW)) { if (vertex.OutEdges(memgraph::storage::View::NEW).HasValue()) { auto maybe_edges = vertex.OutEdges(memgraph::storage::View::NEW); for (auto edge : *maybe_edges) { @@ -2274,7 +2229,7 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { ASSERT_TRUE(maybe_properties.HasValue()); const auto &properties = *maybe_properties; EXPECT_EQ(properties.size(), 1); - auto maybe_prop = edge.GetProperty(memgraph::storage::View::NEW, entity_prop); + auto maybe_prop = edge.GetProperty(memgraph::storage::View::NEW, this->entity_prop); ASSERT_TRUE(maybe_prop.HasValue()); ASSERT_EQ(maybe_prop->ValueInt(), expected_property_value); } @@ -2283,7 +2238,7 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { }; auto test_remove_hypothesis = [&](int properties_size) { - for (auto vertex : dba.Vertices(memgraph::storage::View::NEW)) { + for (auto vertex : this->dba.Vertices(memgraph::storage::View::NEW)) { if (vertex.OutEdges(memgraph::storage::View::NEW).HasValue()) { auto maybe_edges = vertex.OutEdges(memgraph::storage::View::NEW); for (auto edge : *maybe_edges) { @@ -2304,16 +2259,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(1); } @@ -2324,16 +2279,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant(edge_type_name, memgraph::auth::FineGrainedPermission::NOTHING); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(1); } @@ -2344,16 +2299,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(0); } @@ -2364,16 +2319,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant(edge_type_name, memgraph::auth::FineGrainedPermission::UPDATE); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(0); } @@ -2386,16 +2341,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(0); } @@ -2406,16 +2361,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant(edge_type_name, memgraph::auth::FineGrainedPermission::READ); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteSetPropertyOnEdge(user, 2), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteSetPropertyOnEdge(user, 2), QueryRuntimeException); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteSetPropertiesOnEdge(user, 2), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteSetPropertiesOnEdge(user, 2), QueryRuntimeException); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteRemovePropertyOnEdge(user), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteRemovePropertyOnEdge(user), QueryRuntimeException); test_remove_hypothesis(1); } @@ -2425,16 +2380,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteSetPropertyOnEdge(user, 2), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteSetPropertyOnEdge(user, 2), QueryRuntimeException); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteSetPropertiesOnEdge(user, 2), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteSetPropertiesOnEdge(user, 2), QueryRuntimeException); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ASSERT_THROW(ExecuteRemovePropertyOnEdge(user), QueryRuntimeException); + this->SetEdgeProperty(edge.GetValue()); + ASSERT_THROW(this->ExecuteRemovePropertyOnEdge(user), QueryRuntimeException); test_remove_hypothesis(1); } @@ -2447,16 +2402,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(1); } @@ -2469,16 +2424,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(2); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(0); } @@ -2491,16 +2446,16 @@ TEST_F(UpdatePropertiesWithAuthFixture, SetPropertyExpandWithAuthChecker) { user.fine_grained_access_handler().edge_type_permissions().Grant( "*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertyOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertyOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteSetPropertiesOnEdge(user, 2); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteSetPropertiesOnEdge(user, 2); test_hypothesis(1); - SetEdgeProperty(edge.GetValue()); - ExecuteRemovePropertyOnEdge(user); + this->SetEdgeProperty(edge.GetValue()); + this->ExecuteRemovePropertyOnEdge(user); test_remove_hypothesis(1); } } diff --git a/tests/unit/query_plan_edge_cases.cpp b/tests/unit/query_plan_edge_cases.cpp index fe9f0e5f5..9d09918e2 100644 --- a/tests/unit/query_plan_edge_cases.cpp +++ b/tests/unit/query_plan_edge_cases.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,35 +14,42 @@ // easy testing and latter readability they are tested end-to-end. #include <filesystem> +#include <memory> #include <optional> +#include "disk_test_utils.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "communication/result_stream_faker.hpp" #include "query/interpreter.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" DECLARE_bool(query_cost_planner); +template <typename StorageType> class QueryExecution : public testing::Test { protected: - std::optional<memgraph::storage::Storage> db_; + const std::string testSuite = "query_plan_edge_cases"; std::optional<memgraph::query::InterpreterContext> interpreter_context_; std::optional<memgraph::query::Interpreter> interpreter_; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_plan_edge_cases"}; void SetUp() { - db_.emplace(); - interpreter_context_.emplace(&*db_, memgraph::query::InterpreterConfig{}, data_directory); + interpreter_context_.emplace(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), + memgraph::query::InterpreterConfig{}, data_directory); interpreter_.emplace(&*interpreter_context_); } void TearDown() { interpreter_ = std::nullopt; interpreter_context_ = std::nullopt; - db_ = std::nullopt; + + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } } /** @@ -51,7 +58,7 @@ class QueryExecution : public testing::Test { * Return the query results. */ auto Execute(const std::string &query) { - ResultStreamFaker stream(&*db_); + ResultStreamFaker stream(this->interpreter_context_->db.get()); auto [header, _, qid] = interpreter_->Prepare(query, {}, nullptr); stream.Header(header); @@ -62,24 +69,28 @@ class QueryExecution : public testing::Test { } }; -TEST_F(QueryExecution, MissingOptionalIntoExpand) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryExecution, StorageTypes); + +TYPED_TEST(QueryExecution, MissingOptionalIntoExpand) { // validating bug where expanding from Null (due to a preceeding optional // match) exhausts the expansion cursor, even if it's input is still not // exhausted - Execute( + this->Execute( "CREATE (a:Person {id: 1}), (b:Person " "{id:2})-[:Has]->(:Dog)-[:Likes]->(:Food )"); - ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 4); + ASSERT_EQ(this->Execute("MATCH (n) RETURN n").size(), 4); auto Exec = [this](bool desc, const std::string &edge_pattern) { // this test depends on left-to-right query planning FLAGS_query_cost_planner = false; - return Execute(std::string("MATCH (p:Person) WITH p ORDER BY p.id ") + (desc ? "DESC " : "") + - "OPTIONAL MATCH (p)-->(d:Dog) WITH p, d " - "MATCH (d)" + - edge_pattern + - "(f:Food) " - "RETURN p, d, f") + return this + ->Execute(std::string("MATCH (p:Person) WITH p ORDER BY p.id ") + (desc ? "DESC " : "") + + "OPTIONAL MATCH (p)-->(d:Dog) WITH p, d " + "MATCH (d)" + + edge_pattern + + "(f:Food) " + "RETURN p, d, f") .size(); }; @@ -94,14 +105,14 @@ TEST_F(QueryExecution, MissingOptionalIntoExpand) { EXPECT_EQ(Exec(true, bfs), 1); } -TEST_F(QueryExecution, EdgeUniquenessInOptional) { +TYPED_TEST(QueryExecution, EdgeUniquenessInOptional) { // Validating that an edge uniqueness check can't fail when the edge is Null // due to optonal match. Since edge-uniqueness only happens in one OPTIONAL // MATCH, we only need to check that scenario. - Execute("CREATE (), ()-[:Type]->()"); - ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 3); - EXPECT_EQ(Execute("MATCH (n) OPTIONAL MATCH (n)-[r1]->(), (n)-[r2]->() " - "RETURN n, r1, r2") + this->Execute("CREATE (), ()-[:Type]->()"); + ASSERT_EQ(this->Execute("MATCH (n) RETURN n").size(), 3); + EXPECT_EQ(this->Execute("MATCH (n) OPTIONAL MATCH (n)-[r1]->(), (n)-[r2]->() " + "RETURN n, r1, r2") .size(), 3); } diff --git a/tests/unit/query_plan_match_filter_return.cpp b/tests/unit/query_plan_match_filter_return.cpp index f6a1ede52..e4e240a0f 100644 --- a/tests/unit/query_plan_match_filter_return.cpp +++ b/tests/unit/query_plan_match_filter_return.cpp @@ -9,6 +9,8 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include "disk_test_utils.hpp" +#include "query/frontend/ast/ast.hpp" #include "query_plan_common.hpp" #include <iterator> @@ -34,23 +36,36 @@ #include "query/context.hpp" #include "query/exceptions.hpp" #include "query/plan/operator.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "utils/synchronized.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; +const std::string testSuite = "query_plan_match_filter_return"; + +template <typename StorageType> class MatchReturnFixture : public testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; + void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + void AddVertices(int count) { for (int i = 0; i < count; i++) dba.InsertVertex(); } - void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); } std::vector<Path> PathResults(std::shared_ptr<Produce> &op) { std::vector<Path> res; @@ -72,60 +87,63 @@ class MatchReturnFixture : public testing::Test { } }; -TEST_F(MatchReturnFixture, MatchReturn) { - AddVertices(2); - dba.AdvanceCommand(); +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(MatchReturnFixture, StorageTypes); + +TYPED_TEST(MatchReturnFixture, MatchReturn) { + this->AddVertices(2); + this->dba.AdvanceCommand(); auto test_pull_count = [&](memgraph::storage::View view) { - auto scan_all = MakeScanAll(storage, symbol_table, "n", nullptr, view); - auto output = - NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto scan_all = MakeScanAll(this->storage, this->symbol_table, "n", nullptr, view); + auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_)) + ->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(scan_all.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); return PullAll(*produce, &context); }; EXPECT_EQ(2, test_pull_count(memgraph::storage::View::NEW)); EXPECT_EQ(2, test_pull_count(memgraph::storage::View::OLD)); - dba.InsertVertex(); + this->dba.InsertVertex(); EXPECT_EQ(3, test_pull_count(memgraph::storage::View::NEW)); EXPECT_EQ(2, test_pull_count(memgraph::storage::View::OLD)); - dba.AdvanceCommand(); + this->dba.AdvanceCommand(); EXPECT_EQ(3, test_pull_count(memgraph::storage::View::OLD)); } -TEST_F(MatchReturnFixture, MatchReturnPath) { - AddVertices(2); - dba.AdvanceCommand(); +TYPED_TEST(MatchReturnFixture, MatchReturnPath) { + this->AddVertices(2); + this->dba.AdvanceCommand(); - auto scan_all = MakeScanAll(storage, symbol_table, "n", nullptr); - Symbol path_sym = symbol_table.CreateSymbol("path", true); + auto scan_all = MakeScanAll(this->storage, this->symbol_table, "n", nullptr); + Symbol path_sym = this->symbol_table.CreateSymbol("path", true); auto make_path = std::make_shared<ConstructNamedPath>(scan_all.op_, path_sym, std::vector<Symbol>{scan_all.sym_}); auto output = - NEXPR("path", IDENT("path")->MapTo(path_sym))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + NEXPR("path", IDENT("path")->MapTo(path_sym))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(make_path, output); - auto results = PathResults(produce); + auto results = this->PathResults(produce); ASSERT_EQ(results.size(), 2); std::vector<memgraph::query::Path> expected_paths; - for (const auto &v : dba.Vertices(memgraph::storage::View::OLD)) expected_paths.emplace_back(v); + for (const auto &v : this->dba.Vertices(memgraph::storage::View::OLD)) expected_paths.emplace_back(v); ASSERT_EQ(expected_paths.size(), 2); EXPECT_TRUE(std::is_permutation(expected_paths.begin(), expected_paths.end(), results.begin())); } #ifdef MG_ENTERPRISE -TEST_F(MatchReturnFixture, ScanAllWithAuthChecker) { +TYPED_TEST(MatchReturnFixture, ScanAllWithAuthChecker) { std::string labelName = "l1"; - const auto label = dba.NameToLabel(labelName); + const auto label = this->dba.NameToLabel(labelName); - ASSERT_TRUE(dba.InsertVertex().AddLabel(label).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(this->dba.InsertVertex().AddLabel(label).HasValue()); + this->dba.AdvanceCommand(); auto test_hypothesis = [&](memgraph::auth::User user, memgraph::storage::View view, int expected_pull_count) { - auto scan_all = MakeScanAll(storage, symbol_table, "n", nullptr, view); - ASSERT_EQ(expected_pull_count, PullCountAuthorized(scan_all, user)); + auto scan_all = MakeScanAll(this->storage, this->symbol_table, "n", nullptr, view); + ASSERT_EQ(expected_pull_count, this->PullCountAuthorized(scan_all, user)); - scan_all = MakeScanAll(storage, symbol_table, "n", nullptr, view); - ASSERT_EQ(expected_pull_count, PullCountAuthorized(scan_all, user)); + scan_all = MakeScanAll(this->storage, this->symbol_table, "n", nullptr, view); + ASSERT_EQ(expected_pull_count, this->PullCountAuthorized(scan_all, user)); }; { @@ -204,24 +222,38 @@ TEST_F(MatchReturnFixture, ScanAllWithAuthChecker) { } #endif -TEST(QueryPlan, MatchReturnCartesian) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +template <typename StorageType> +class QueryPlan : public testing::Test { + public: + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +TYPED_TEST_CASE(QueryPlan, StorageTypes); + +TYPED_TEST(QueryPlan, MatchReturnCartesian) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); ASSERT_TRUE(dba.InsertVertex().AddLabel(dba.NameToLabel("l1")).HasValue()); ASSERT_TRUE(dba.InsertVertex().AddLabel(dba.NameToLabel("l2")).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto m = MakeScanAll(storage, symbol_table, "m", n.op_); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto m = MakeScanAll(this->storage, symbol_table, "m", n.op_); auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); auto produce = MakeProduce(m.op_, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 4); // ensure the result ordering is OK: @@ -230,38 +262,35 @@ TEST(QueryPlan, MatchReturnCartesian) { EXPECT_NE(results[0][1].ValueVertex(), results[1][1].ValueVertex()); } -TEST(QueryPlan, StandaloneReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, StandaloneReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database dba.InsertVertex(); dba.InsertVertex(); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; auto output = NEXPR("n", LITERAL(42)); auto produce = MakeProduce(std::shared_ptr<LogicalOperator>(nullptr), output); output->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 1); EXPECT_EQ(results[0].size(), 1); EXPECT_EQ(results[0][0].ValueInt(), 42); } -TEST(QueryPlan, NodeFilterLabelsAndProperties) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, NodeFilterLabelsAndProperties) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database memgraph::storage::LabelId label = dba.NameToLabel("Label"); - auto property = PROPERTY_PAIR("Property"); + auto property = PROPERTY_PAIR(dba, "Property"); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); auto v3 = dba.InsertVertex(); @@ -281,24 +310,23 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) { ASSERT_TRUE(v5.SetProperty(property.second, memgraph::storage::PropertyValue(1)).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // make a scan all - auto n = MakeScanAll(storage, symbol_table, "n"); - n.node_->labels_.emplace_back(storage.GetLabelIx(dba.LabelToName(label))); - std::get<0>(n.node_->properties_)[storage.GetPropertyIx(property.first)] = LITERAL(42); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + n.node_->labels_.emplace_back(this->storage.GetLabelIx(dba.LabelToName(label))); + std::get<0>(n.node_->properties_)[this->storage.GetPropertyIx(property.first)] = LITERAL(42); // node filtering - auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), - EQ(PROPERTY_LOOKUP(n.node_->identifier_, property), LITERAL(42))); + auto *filter_expr = AND(this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), + EQ(PROPERTY_LOOKUP(dba, n.node_->identifier_, property), LITERAL(42))); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); // make a named expression and a produce auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(node_filter, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*produce, &context)); // test that filtering works with old records @@ -308,10 +336,9 @@ TEST(QueryPlan, NodeFilterLabelsAndProperties) { EXPECT_EQ(2, PullAll(*produce, &context)); } -TEST(QueryPlan, NodeFilterMultipleLabels) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, NodeFilterMultipleLabels) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database memgraph::storage::LabelId label1 = dba.NameToLabel("label1"); @@ -334,31 +361,29 @@ TEST(QueryPlan, NodeFilterMultipleLabels) { ASSERT_TRUE(v3.AddLabel(label3).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // make a scan all - auto n = MakeScanAll(storage, symbol_table, "n"); - n.node_->labels_.emplace_back(storage.GetLabelIx(dba.LabelToName(label1))); - n.node_->labels_.emplace_back(storage.GetLabelIx(dba.LabelToName(label2))); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + n.node_->labels_.emplace_back(this->storage.GetLabelIx(dba.LabelToName(label1))); + n.node_->labels_.emplace_back(this->storage.GetLabelIx(dba.LabelToName(label2))); // node filtering - auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); + auto *filter_expr = this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); // make a named expression and a produce auto output = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(node_filter, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } -TEST(QueryPlan, Cartesian) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, Cartesian) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto add_vertex = [&dba](std::string label) { auto vertex = dba.InsertVertex(); @@ -369,11 +394,10 @@ TEST(QueryPlan, Cartesian) { std::vector<memgraph::query::VertexAccessor> vertices{add_vertex("v1"), add_vertex("v2"), add_vertex("v3")}; dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto m = MakeScanAll(storage, symbol_table, "m"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto m = MakeScanAll(this->storage, symbol_table, "m"); auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); @@ -383,7 +407,7 @@ TEST(QueryPlan, Cartesian) { auto produce = MakeProduce(cartesian_op, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 9); for (int i = 0; i < 3; ++i) { @@ -394,16 +418,13 @@ TEST(QueryPlan, Cartesian) { } } -TEST(QueryPlan, CartesianEmptySet) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - - AstStorage storage; +TYPED_TEST(QueryPlan, CartesianEmptySet) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto m = MakeScanAll(storage, symbol_table, "m"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto m = MakeScanAll(this->storage, symbol_table, "m"); auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); @@ -412,15 +433,14 @@ TEST(QueryPlan, CartesianEmptySet) { auto cartesian_op = std::make_shared<Cartesian>(n.op_, left_symbols, m.op_, right_symbols); auto produce = MakeProduce(cartesian_op, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 0); } -TEST(QueryPlan, CartesianThreeWay) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, CartesianThreeWay) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto add_vertex = [&dba](std::string label) { auto vertex = dba.InsertVertex(); MG_ASSERT(vertex.AddLabel(dba.NameToLabel(label)).HasValue()); @@ -430,12 +450,11 @@ TEST(QueryPlan, CartesianThreeWay) { std::vector<memgraph::query::VertexAccessor> vertices{add_vertex("v1"), add_vertex("v2"), add_vertex("v3")}; dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto l = MakeScanAll(storage, symbol_table, "l"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto m = MakeScanAll(this->storage, symbol_table, "m"); + auto l = MakeScanAll(this->storage, symbol_table, "l"); auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); auto return_l = NEXPR("l", IDENT("l")->MapTo(l.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_3", true)); @@ -449,7 +468,7 @@ TEST(QueryPlan, CartesianThreeWay) { auto cartesian_op_2 = std::make_shared<Cartesian>(cartesian_op_1, n_m_symbols, l.op_, l_symbols); auto produce = MakeProduce(cartesian_op_2, return_n, return_m, return_l); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 27); int id = 0; @@ -465,19 +484,21 @@ TEST(QueryPlan, CartesianThreeWay) { } } +template <typename StorageType> class ExpandFixture : public testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - AstStorage storage; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; + AstStorage storage; // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2) memgraph::query::VertexAccessor v1{dba.InsertVertex()}; memgraph::query::VertexAccessor v2{dba.InsertVertex()}; memgraph::query::VertexAccessor v3{dba.InsertVertex()}; - memgraph::storage::EdgeTypeId edge_type{db.NameToEdgeType("Edge")}; + memgraph::storage::EdgeTypeId edge_type{db->NameToEdgeType("Edge")}; memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type)}; memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v1, &v3, edge_type)}; @@ -489,48 +510,57 @@ class ExpandFixture : public testing::Test { dba.AdvanceCommand(); } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } }; -TEST_F(ExpandFixture, Expand) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ExpandFixture, StorageTypes); + +TYPED_TEST(ExpandFixture, Expand) { auto test_expand = [&](EdgeAtom::Direction direction, memgraph::storage::View view) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", direction, {}, "m", false, view); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto r_m = MakeExpand(this->storage, this->symbol_table, n.op_, n.sym_, "r", direction, {}, "m", false, view); // make a named expression and a produce - auto output = - NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto output = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_)) + ->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(r_m.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); return PullAll(*produce, &context); }; // test that expand works well for both old and new graph state - ASSERT_TRUE(dba.InsertEdge(&v1, &v2, edge_type).HasValue()); - ASSERT_TRUE(dba.InsertEdge(&v1, &v3, edge_type).HasValue()); + ASSERT_TRUE(this->dba.InsertEdge(&this->v1, &this->v2, this->edge_type).HasValue()); + ASSERT_TRUE(this->dba.InsertEdge(&this->v1, &this->v3, this->edge_type).HasValue()); EXPECT_EQ(2, test_expand(EdgeAtom::Direction::OUT, memgraph::storage::View::OLD)); EXPECT_EQ(2, test_expand(EdgeAtom::Direction::IN, memgraph::storage::View::OLD)); EXPECT_EQ(4, test_expand(EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD)); EXPECT_EQ(4, test_expand(EdgeAtom::Direction::OUT, memgraph::storage::View::NEW)); EXPECT_EQ(4, test_expand(EdgeAtom::Direction::IN, memgraph::storage::View::NEW)); EXPECT_EQ(8, test_expand(EdgeAtom::Direction::BOTH, memgraph::storage::View::NEW)); - dba.AdvanceCommand(); + this->dba.AdvanceCommand(); EXPECT_EQ(4, test_expand(EdgeAtom::Direction::OUT, memgraph::storage::View::OLD)); EXPECT_EQ(4, test_expand(EdgeAtom::Direction::IN, memgraph::storage::View::OLD)); EXPECT_EQ(8, test_expand(EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD)); } #ifdef MG_ENTERPRISE -TEST_F(ExpandFixture, ExpandWithEdgeFiltering) { +TYPED_TEST(ExpandFixture, ExpandWithEdgeFiltering) { auto test_expand = [&](memgraph::auth::User user, EdgeAtom::Direction direction, memgraph::storage::View view) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", direction, {}, "m", false, view); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto r_m = MakeExpand(this->storage, this->symbol_table, n.op_, n.sym_, "r", direction, {}, "m", false, view); // make a named expression and a produce - auto output = - NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto output = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_)) + ->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(r_m.op_, output); - memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; - auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &this->dba}; + auto context = MakeContextWithFineGrainedChecker(this->storage, this->symbol_table, &this->dba, &auth_checker); return PullAll(*produce, &context); }; @@ -542,10 +572,10 @@ TEST_F(ExpandFixture, ExpandWithEdgeFiltering) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - memgraph::storage::EdgeTypeId edge_type_test{db.NameToEdgeType("edge_type_test")}; + memgraph::storage::EdgeTypeId edge_type_test{this->db->NameToEdgeType("edge_type_test")}; - ASSERT_TRUE(dba.InsertEdge(&v1, &v2, edge_type_test).HasValue()); - ASSERT_TRUE(dba.InsertEdge(&v1, &v3, edge_type_test).HasValue()); + ASSERT_TRUE(this->dba.InsertEdge(&this->v1, &this->v2, edge_type_test).HasValue()); + ASSERT_TRUE(this->dba.InsertEdge(&this->v1, &this->v3, edge_type_test).HasValue()); // test that expand works well for both old and new graph state EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD)); EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD)); @@ -554,7 +584,7 @@ TEST_F(ExpandFixture, ExpandWithEdgeFiltering) { EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::NEW)); EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::NEW)); - dba.AdvanceCommand(); + this->dba.AdvanceCommand(); EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD)); EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD)); @@ -576,20 +606,20 @@ TEST_F(ExpandFixture, ExpandWithEdgeFiltering) { } #endif -TEST_F(ExpandFixture, ExpandPath) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, +TYPED_TEST(ExpandFixture, ExpandPath) { + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto r_m = MakeExpand(this->storage, this->symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); - Symbol path_sym = symbol_table.CreateSymbol("path", true); + Symbol path_sym = this->symbol_table.CreateSymbol("path", true); auto path = std::make_shared<ConstructNamedPath>(r_m.op_, path_sym, std::vector<Symbol>{n.sym_, r_m.edge_sym_, r_m.node_sym_}); auto output = - NEXPR("path", IDENT("path")->MapTo(path_sym))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + NEXPR("path", IDENT("path")->MapTo(path_sym))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(path, output); - std::vector<memgraph::query::Path> expected_paths{memgraph::query::Path(v1, r2, v3), - memgraph::query::Path(v1, r1, v2)}; - auto context = MakeContext(storage, symbol_table, &dba); + std::vector<memgraph::query::Path> expected_paths{memgraph::query::Path(this->v1, this->r2, this->v3), + memgraph::query::Path(this->v1, this->r1, this->v2)}; + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(results.size(), 2); std::vector<memgraph::query::Path> results_paths; @@ -609,15 +639,18 @@ TEST_F(ExpandFixture, ExpandPath) { * member in this class). Edges have properties set that * indicate origin and destination vertex for debugging. */ +using map_int = std::unordered_map<int, int>; + +template <typename StorageType> class QueryPlanExpandVariable : public testing::Test { protected: // type returned by the GetEdgeListSizes function, used // a lot below in test declaration - using map_int = std::unordered_map<int, int>; - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; // labels for layers in the double chain std::vector<memgraph::storage::LabelId> labels; @@ -627,7 +660,7 @@ class QueryPlanExpandVariable : public testing::Test { // using std::nullopt std::nullopt_t nullopt = std::nullopt; - void SetUp() { + void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); // create the graph @@ -659,6 +692,12 @@ class QueryPlanExpandVariable : public testing::Test { ASSERT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 4 * (chain_length - 1)); } + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + /** * Expands the given LogicalOperator input with a match * (ScanAll->Filter(label)->Expand). Can create both VariableExpand @@ -773,13 +812,17 @@ class QueryPlanExpandVariable : public testing::Test { } }; -TEST_F(QueryPlanExpandVariable, OneVariableExpansion) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlanExpandVariable, StorageTypes); + +TYPED_TEST(QueryPlanExpandVariable, OneVariableExpansion) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool reverse) { - auto e = Edge("r", direction); - return GetEdgeListSizes(AddMatch<ExpandVariable>(nullptr, "n", layer, direction, {}, lower, upper, e, "m", - memgraph::storage::View::OLD, reverse), - e); + auto e = this->Edge("r", direction); + return this->GetEdgeListSizes( + this->template AddMatch<ExpandVariable>(nullptr, "n", layer, direction, {}, lower, upper, e, "m", + memgraph::storage::View::OLD, reverse), + e); }; for (int reverse = 0; reverse < 2; ++reverse) { @@ -800,23 +843,24 @@ TEST_F(QueryPlanExpandVariable, OneVariableExpansion) { EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 4, 4, reverse), (map_int{{4, 24}})); // default bound values (lower default is 1, upper default is inf) - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 0, reverse), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 1, reverse), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 2, reverse), (map_int{{1, 4}, {2, 8}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 7, nullopt, reverse), (map_int{{7, 24}, {8, 24}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 8, nullopt, reverse), (map_int{{8, 24}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 9, nullopt, reverse), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 0, reverse), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 1, reverse), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 2, reverse), (map_int{{1, 4}, {2, 8}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 7, this->nullopt, reverse), (map_int{{7, 24}, {8, 24}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 8, this->nullopt, reverse), (map_int{{8, 24}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 9, this->nullopt, reverse), (map_int{})); } } #ifdef MG_ENTERPRISE -TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { +TYPED_TEST(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool reverse, memgraph::auth::User &user) { - auto e = Edge("r", direction); - return GetEdgeListSizes(AddMatch<ExpandVariable>(nullptr, "n", layer, direction, {}, lower, upper, e, "m", - memgraph::storage::View::OLD, reverse), - e, &user); + auto e = this->Edge("r", direction); + return this->GetEdgeListSizes( + this->template AddMatch<ExpandVariable>(nullptr, "n", layer, direction, {}, lower, upper, e, "m", + memgraph::storage::View::OLD, reverse), + e, &user); }; // All labels, All edge types granted @@ -843,12 +887,13 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 4, 4, reverse, user), (map_int{{4, 24}})); // default bound values (lower default is 1, upper default is inf) - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 0, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 1, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, nullopt, 2, reverse, user), (map_int{{1, 4}, {2, 8}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 7, nullopt, reverse, user), (map_int{{7, 24}, {8, 24}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 8, nullopt, reverse, user), (map_int{{8, 24}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 9, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 0, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 1, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, this->nullopt, 2, reverse, user), (map_int{{1, 4}, {2, 8}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 7, this->nullopt, reverse, user), + (map_int{{7, 24}, {8, 24}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 8, this->nullopt, reverse, user), (map_int{{8, 24}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 9, this->nullopt, reverse, user), (map_int{})); } } @@ -859,12 +904,12 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); } } @@ -875,18 +920,18 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); } } @@ -896,12 +941,12 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); } } @@ -914,18 +959,18 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::NOTHING); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 2, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 2, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 2, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 2, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 2, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 2, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); } } @@ -939,20 +984,20 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{{1, 4}, {2, 4}, {3, 4}, {4, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{})); } } @@ -965,21 +1010,21 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::NOTHING); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}, {1, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}, {1, 4}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{{1, 4}, {2, 4}, {3, 4}, {4, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}})); } } @@ -994,48 +1039,48 @@ TEST_F(QueryPlanExpandVariable, FineGrainedOneVariableExpansion) { memgraph::auth::FineGrainedPermission::NOTHING); for (auto reverse : {false, true}) { - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, nullopt, reverse, user), (map_int{{0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, nullopt, reverse, user), + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 0, this->nullopt, reverse, user), (map_int{{1, 4}, {0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 0, this->nullopt, reverse, user), (map_int{{0, 2}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 0, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}, {0, 2}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(1, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{{1, 4}, {2, 4}, {3, 4}, {4, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, nullopt, reverse, user), (map_int{})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, nullopt, reverse, user), (map_int{{1, 4}})); - EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, nullopt, reverse, user), + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::IN, 1, this->nullopt, reverse, user), (map_int{})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 1, this->nullopt, reverse, user), (map_int{{1, 4}})); + EXPECT_EQ(test_expand(0, EdgeAtom::Direction::BOTH, 1, this->nullopt, reverse, user), (map_int{{4, 4}, {3, 4}, {2, 4}, {1, 4}})); } } } #endif -TEST_F(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) { +TYPED_TEST(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool single_expansion_before, bool add_uniqueness_check) { std::shared_ptr<LogicalOperator> last_op{nullptr}; std::vector<Symbol> symbols; if (single_expansion_before) { - symbols.push_back(Edge("r0", direction)); - last_op = AddMatch<Expand>(last_op, "n0", layer, direction, {}, lower, upper, symbols.back(), "m0", - memgraph::storage::View::OLD); + symbols.push_back(this->Edge("r0", direction)); + last_op = this->template AddMatch<Expand>(last_op, "n0", layer, direction, {}, lower, upper, symbols.back(), "m0", + memgraph::storage::View::OLD); } - auto var_length_sym = Edge("r1", direction); + auto var_length_sym = this->Edge("r1", direction); symbols.push_back(var_length_sym); - last_op = AddMatch<ExpandVariable>(last_op, "n1", layer, direction, {}, lower, upper, var_length_sym, "m1", - memgraph::storage::View::OLD); + last_op = this->template AddMatch<ExpandVariable>(last_op, "n1", layer, direction, {}, lower, upper, var_length_sym, + "m1", memgraph::storage::View::OLD); if (!single_expansion_before) { - symbols.push_back(Edge("r2", direction)); - last_op = AddMatch<Expand>(last_op, "n2", layer, direction, {}, lower, upper, symbols.back(), "m2", - memgraph::storage::View::OLD); + symbols.push_back(this->Edge("r2", direction)); + last_op = this->template AddMatch<Expand>(last_op, "n2", layer, direction, {}, lower, upper, symbols.back(), "m2", + memgraph::storage::View::OLD); } if (add_uniqueness_check) { @@ -1044,7 +1089,7 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) { last_op = std::make_shared<EdgeUniquenessFilter>(last_op, last_symbol, symbols); } - return GetEdgeListSizes(last_op, var_length_sym); + return this->GetEdgeListSizes(last_op, var_length_sym); }; // no uniqueness between variable and single expansion @@ -1054,20 +1099,20 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessSingleAndVariableExpansion) { EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 2, 3, false, true), (map_int{{2, 3 * 8}})); } -TEST_F(QueryPlanExpandVariable, EdgeUniquenessTwoVariableExpansions) { +TYPED_TEST(QueryPlanExpandVariable, EdgeUniquenessTwoVariableExpansions) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool add_uniqueness_check) { - auto e1 = Edge("r1", direction); - auto first = AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {}, lower, upper, e1, "m1", - memgraph::storage::View::OLD); - auto e2 = Edge("r2", direction); - auto last_op = AddMatch<ExpandVariable>(first, "n2", layer, direction, {}, lower, upper, e2, "m2", - memgraph::storage::View::OLD); + auto e1 = this->Edge("r1", direction); + auto first = this->template AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {}, lower, upper, e1, "m1", + memgraph::storage::View::OLD); + auto e2 = this->Edge("r2", direction); + auto last_op = this->template AddMatch<ExpandVariable>(first, "n2", layer, direction, {}, lower, upper, e2, "m2", + memgraph::storage::View::OLD); if (add_uniqueness_check) { last_op = std::make_shared<EdgeUniquenessFilter>(last_op, e2, std::vector<Symbol>{e1}); } - return GetEdgeListSizes(last_op, e2); + return this->GetEdgeListSizes(last_op, e2); }; EXPECT_EQ(test_expand(0, EdgeAtom::Direction::OUT, 2, 2, false), (map_int{{2, 8 * 8}})); @@ -1075,20 +1120,20 @@ TEST_F(QueryPlanExpandVariable, EdgeUniquenessTwoVariableExpansions) { } #ifdef MG_ENTERPRISE -TEST_F(QueryPlanExpandVariable, FineGrainedEdgeUniquenessTwoVariableExpansions) { +TYPED_TEST(QueryPlanExpandVariable, FineGrainedEdgeUniquenessTwoVariableExpansions) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool add_uniqueness_check, memgraph::auth::User &user) { - auto e1 = Edge("r1", direction); - auto first = AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {}, lower, upper, e1, "m1", - memgraph::storage::View::OLD); - auto e2 = Edge("r2", direction); - auto last_op = AddMatch<ExpandVariable>(first, "n2", layer, direction, {}, lower, upper, e2, "m2", - memgraph::storage::View::OLD); + auto e1 = this->Edge("r1", direction); + auto first = this->template AddMatch<ExpandVariable>(nullptr, "n1", layer, direction, {}, lower, upper, e1, "m1", + memgraph::storage::View::OLD); + auto e2 = this->Edge("r2", direction); + auto last_op = this->template AddMatch<ExpandVariable>(first, "n2", layer, direction, {}, lower, upper, e2, "m2", + memgraph::storage::View::OLD); if (add_uniqueness_check) { last_op = std::make_shared<EdgeUniquenessFilter>(last_op, e2, std::vector<Symbol>{e1}); } - return GetEdgeListSizes(last_op, e2, &user); + return this->GetEdgeListSizes(last_op, e2, &user); }; // All labels granted, All edge types granted @@ -1168,23 +1213,23 @@ TEST_F(QueryPlanExpandVariable, FineGrainedEdgeUniquenessTwoVariableExpansions) } #endif -TEST_F(QueryPlanExpandVariable, NamedPath) { - auto e = Edge("r", EdgeAtom::Direction::OUT); - auto expand = AddMatch<ExpandVariable>(nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 2, 2, e, "m", - memgraph::storage::View::OLD); +TYPED_TEST(QueryPlanExpandVariable, NamedPath) { + auto e = this->Edge("r", EdgeAtom::Direction::OUT); + auto expand = this->template AddMatch<ExpandVariable>(nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 2, 2, e, "m", + memgraph::storage::View::OLD); auto find_symbol = [this](const std::string &name) { - for (const auto &sym : symbol_table.table()) + for (const auto &sym : this->symbol_table.table()) if (sym.second.name() == name) return sym.second; throw std::runtime_error("Symbol not found"); }; - auto path_symbol = symbol_table.CreateSymbol("path", true, Symbol::Type::PATH); + auto path_symbol = this->symbol_table.CreateSymbol("path", true, Symbol::Type::PATH); auto create_path = std::make_shared<ConstructNamedPath>(expand, path_symbol, std::vector<Symbol>{find_symbol("n"), e, find_symbol("m")}); std::vector<memgraph::query::Path> expected_paths; - for (const auto &v : dba.Vertices(memgraph::storage::View::OLD)) { - if (!*v.HasLabel(memgraph::storage::View::OLD, labels[0])) continue; + for (const auto &v : this->dba.Vertices(memgraph::storage::View::OLD)) { + if (!*v.HasLabel(memgraph::storage::View::OLD, this->labels[0])) continue; auto maybe_edges1 = v.OutEdges(memgraph::storage::View::OLD); for (const auto &e1 : *maybe_edges1) { auto maybe_edges2 = e1.To().OutEdges(memgraph::storage::View::OLD); @@ -1195,23 +1240,23 @@ TEST_F(QueryPlanExpandVariable, NamedPath) { } ASSERT_EQ(expected_paths.size(), 8); - auto results = GetPathResults(create_path, path_symbol); + auto results = this->GetPathResults(create_path, path_symbol); ASSERT_EQ(results.size(), 8); EXPECT_TRUE(std::is_permutation(results.begin(), results.end(), expected_paths.begin())); } #ifdef MG_ENTERPRISE -TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { - auto e = Edge("r", EdgeAtom::Direction::OUT); - auto expand = AddMatch<ExpandVariable>(nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 0, 2, e, "m", - memgraph::storage::View::OLD); +TYPED_TEST(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { + auto e = this->Edge("r", EdgeAtom::Direction::OUT); + auto expand = this->template AddMatch<ExpandVariable>(nullptr, "n", 0, EdgeAtom::Direction::OUT, {}, 0, 2, e, "m", + memgraph::storage::View::OLD); auto find_symbol = [this](const std::string &name) { - for (const auto &sym : symbol_table.table()) + for (const auto &sym : this->symbol_table.table()) if (sym.second.name() == name) return sym.second; throw std::runtime_error("Symbol not found"); }; - auto path_symbol = symbol_table.CreateSymbol("path", true, Symbol::Type::PATH); + auto path_symbol = this->symbol_table.CreateSymbol("path", true, Symbol::Type::PATH); auto create_path = std::make_shared<ConstructNamedPath>(expand, path_symbol, std::vector<Symbol>{find_symbol("n"), e, find_symbol("m")}); @@ -1220,7 +1265,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 14); } @@ -1231,7 +1276,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 0); } @@ -1241,7 +1286,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 0); } @@ -1252,7 +1297,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 2); } @@ -1264,7 +1309,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 0); } @@ -1276,7 +1321,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 2); } @@ -1288,12 +1333,12 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::NOTHING); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 6); std::vector<memgraph::query::Path> expected_paths; - for (const auto &v : dba.Vertices(memgraph::storage::View::OLD)) { - if (!*v.HasLabel(memgraph::storage::View::OLD, labels[0])) continue; + for (const auto &v : this->dba.Vertices(memgraph::storage::View::OLD)) { + if (!*v.HasLabel(memgraph::storage::View::OLD, this->labels[0])) continue; expected_paths.emplace_back(v); auto maybe_edges1 = v.OutEdges(memgraph::storage::View::OLD); for (const auto &e1 : *maybe_edges1) { @@ -1312,7 +1357,7 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 2); } @@ -1325,12 +1370,12 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = GetPathResults(create_path, path_symbol, &user); + auto results = this->GetPathResults(create_path, path_symbol, &user); ASSERT_EQ(results.size(), 6); std::vector<memgraph::query::Path> expected_paths; - for (const auto &v : dba.Vertices(memgraph::storage::View::OLD)) { - if (!*v.HasLabel(memgraph::storage::View::OLD, labels[0])) continue; + for (const auto &v : this->dba.Vertices(memgraph::storage::View::OLD)) { + if (!*v.HasLabel(memgraph::storage::View::OLD, this->labels[0])) continue; expected_paths.emplace_back(v); auto maybe_edges1 = v.OutEdges(memgraph::storage::View::OLD); for (const auto &e1 : *maybe_edges1) { @@ -1343,35 +1388,37 @@ TEST_F(QueryPlanExpandVariable, FineGrainedFilterNamedPath) { } #endif -TEST_F(QueryPlanExpandVariable, ExpandToSameSymbol) { +TYPED_TEST(QueryPlanExpandVariable, ExpandToSameSymbol) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool reverse) { - auto e = Edge("r", direction); + auto e = this->Edge("r", direction); auto node = NODE("n"); - auto symbol = symbol_table.CreateSymbol("n", true); + auto symbol = this->symbol_table.CreateSymbol("n", true); node->identifier_->MapTo(symbol); auto logical_op = std::make_shared<ScanAll>(nullptr, symbol, memgraph::storage::View::OLD); auto n_from = ScanAllTuple{node, logical_op, symbol}; auto filter_op = std::make_shared<Filter>( n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, - storage.Create<memgraph::query::LabelsTest>( - n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))})); + this->storage.template Create<memgraph::query::LabelsTest>( + n_from.node_->identifier_, + std::vector<LabelIx>{this->storage.GetLabelIx(this->dba.LabelToName(this->labels[layer]))})); // convert optional ints to optional expressions auto convert = [this](std::optional<size_t> bound) { return bound ? LITERAL(static_cast<int64_t>(bound.value())) : nullptr; }; - return GetEdgeListSizes(std::make_shared<ExpandVariable>( - filter_op, symbol, symbol, e, EdgeAtom::Type::DEPTH_FIRST, direction, - std::vector<memgraph::storage::EdgeTypeId>{}, reverse, convert(lower), convert(upper), - /* existing = */ true, - ExpansionLambda{symbol_table.CreateSymbol("inner_edge", false), - symbol_table.CreateSymbol("inner_node", false), nullptr}, - std::nullopt, std::nullopt), - e); + return this->GetEdgeListSizes( + std::make_shared<ExpandVariable>(filter_op, symbol, symbol, e, EdgeAtom::Type::DEPTH_FIRST, direction, + std::vector<memgraph::storage::EdgeTypeId>{}, reverse, convert(lower), + convert(upper), + /* existing = */ true, + ExpansionLambda{this->symbol_table.CreateSymbol("inner_edge", false), + this->symbol_table.CreateSymbol("inner_node", false), nullptr}, + std::nullopt, std::nullopt), + e); }; // The graph is a double chain: @@ -1534,35 +1581,37 @@ TEST_F(QueryPlanExpandVariable, ExpandToSameSymbol) { } #ifdef MG_ENTERPRISE -TEST_F(QueryPlanExpandVariable, FineGrainedExpandToSameSymbol) { +TYPED_TEST(QueryPlanExpandVariable, FineGrainedExpandToSameSymbol) { auto test_expand = [&](int layer, EdgeAtom::Direction direction, std::optional<size_t> lower, std::optional<size_t> upper, bool reverse, memgraph::auth::User &user) { - auto e = Edge("r", direction); + auto e = this->Edge("r", direction); auto node = NODE("n"); - auto symbol = symbol_table.CreateSymbol("n", true); + auto symbol = this->symbol_table.CreateSymbol("n", true); node->identifier_->MapTo(symbol); auto logical_op = std::make_shared<ScanAll>(nullptr, symbol, memgraph::storage::View::OLD); auto n_from = ScanAllTuple{node, logical_op, symbol}; auto filter_op = std::make_shared<Filter>( n_from.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, - storage.Create<memgraph::query::LabelsTest>( - n_from.node_->identifier_, std::vector<LabelIx>{storage.GetLabelIx(dba.LabelToName(labels[layer]))})); + this->storage.template Create<memgraph::query::LabelsTest>( + n_from.node_->identifier_, + std::vector<LabelIx>{this->storage.GetLabelIx(this->dba.LabelToName(this->labels[layer]))})); // convert optional ints to optional expressions auto convert = [this](std::optional<size_t> bound) { return bound ? LITERAL(static_cast<int64_t>(bound.value())) : nullptr; }; - return GetEdgeListSizes(std::make_shared<ExpandVariable>( - filter_op, symbol, symbol, e, EdgeAtom::Type::DEPTH_FIRST, direction, - std::vector<memgraph::storage::EdgeTypeId>{}, reverse, convert(lower), convert(upper), - /* existing = */ true, - ExpansionLambda{symbol_table.CreateSymbol("inner_edge", false), - symbol_table.CreateSymbol("inner_node", false), nullptr}, - std::nullopt, std::nullopt), - e, &user); + return this->GetEdgeListSizes( + std::make_shared<ExpandVariable>(filter_op, symbol, symbol, e, EdgeAtom::Type::DEPTH_FIRST, direction, + std::vector<memgraph::storage::EdgeTypeId>{}, reverse, convert(lower), + convert(upper), + /* existing = */ true, + ExpansionLambda{this->symbol_table.CreateSymbol("inner_edge", false), + this->symbol_table.CreateSymbol("inner_node", false), nullptr}, + std::nullopt, std::nullopt), + e, &user); }; // All labels granted, All edge types granted @@ -1743,6 +1792,7 @@ struct hash<std::pair<int, int>> { } // namespace std /** A test fixture for weighted shortest path expansion */ +template <typename StorageType> class QueryPlanExpandWeightedShortestPath : public testing::Test { public: struct ResultType { @@ -1752,10 +1802,11 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { }; protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR("property"); + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; + std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); // make 5 vertices because we'll need to compare against them exactly @@ -1777,14 +1828,14 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { Symbol total_weight = symbol_table.CreateSymbol("total_weight", true); - void SetUp() { + void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); for (int i = 0; i < 5; i++) { v.push_back(dba.InsertVertex()); ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(i)).HasValue()); auto label = fmt::format("l{}", i); - ASSERT_TRUE(v.back().AddLabel(db.NameToLabel(label)).HasValue()); + ASSERT_TRUE(v.back().AddLabel(db->NameToLabel(label)).HasValue()); } auto add_edge = [&](int from, int to, double weight) { @@ -1803,6 +1854,12 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { dba.AdvanceCommand(); } + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + // defines and performs a weighted shortest expansion with the given // params returns a vector of pairs. each pair is (vector-of-edges, // vertex) @@ -1814,7 +1871,7 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { auto last_op = n.op_; if (node_id) { last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id))); + EQ(PROPERTY_LOOKUP(dba, n.node_->identifier_, prop), LITERAL(*node_id))); } auto ident_e = IDENT("e"); @@ -1827,7 +1884,7 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { last_op, n.sym_, node_sym, edge_list_sym, EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, direction, std::vector<memgraph::storage::EdgeTypeId>{}, false, nullptr, max_depth ? LITERAL(max_depth.value()) : nullptr, existing_node_input != nullptr, ExpansionLambda{filter_edge, filter_node, where}, - ExpansionLambda{weight_edge, weight_node, PROPERTY_LOOKUP(ident_e, prop)}, total_weight); + ExpansionLambda{weight_edge, weight_node, PROPERTY_LOOKUP(dba, ident_e, prop)}, total_weight); Frame frame(symbol_table.max_position()); auto cursor = last_op->MakeCursor(memgraph::utils::NewDeleteResource()); @@ -1865,10 +1922,13 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { Expression *PropNe(Symbol symbol, int value) { auto ident = IDENT("inner_element"); ident->MapTo(symbol); - return NEQ(PROPERTY_LOOKUP(ident, prop), LITERAL(value)); + return NEQ(PROPERTY_LOOKUP(dba, ident, prop), LITERAL(value)); } }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlanExpandWeightedShortestPath, StorageTypes); + // Testing weighted shortest path on this graph: // // 5 5 @@ -1881,221 +1941,222 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { // \->[2]->-[3]->/ // 3 3 3 -TEST_F(QueryPlanExpandWeightedShortestPath, Basic) { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); +TYPED_TEST(QueryPlanExpandWeightedShortestPath, Basic) { + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); ASSERT_EQ(results.size(), 4); // check end nodes - EXPECT_EQ(GetProp(results[0].vertex), 2); - EXPECT_EQ(GetProp(results[1].vertex), 1); - EXPECT_EQ(GetProp(results[2].vertex), 3); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); // check paths and total weights EXPECT_EQ(results[0].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[0].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[0].path[0]), 3); EXPECT_EQ(results[0].total_weight, 3); EXPECT_EQ(results[1].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[1].path[0]), 5); + EXPECT_EQ(this->GetDoubleProp(results[1].path[0]), 5); EXPECT_EQ(results[1].total_weight, 5); EXPECT_EQ(results[2].path.size(), 2); - EXPECT_EQ(GetDoubleProp(results[2].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[2].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[1]), 3); EXPECT_EQ(results[2].total_weight, 6); EXPECT_EQ(results[3].path.size(), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[1]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[2]), 3); EXPECT_EQ(results[3].total_weight, 9); } -TEST_F(QueryPlanExpandWeightedShortestPath, EdgeDirection) { +TYPED_TEST(QueryPlanExpandWeightedShortestPath, EdgeDirection) { { - auto results = ExpandWShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); } { - auto results = ExpandWShortest(EdgeAtom::Direction::IN, 1000, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::IN, 1000, LITERAL(true)); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 4); + EXPECT_EQ(this->GetProp(results[0].vertex), 4); EXPECT_EQ(results[0].total_weight, 12); - EXPECT_EQ(GetProp(results[1].vertex), 3); + EXPECT_EQ(this->GetProp(results[1].vertex), 3); EXPECT_EQ(results[1].total_weight, 15); - EXPECT_EQ(GetProp(results[2].vertex), 1); + EXPECT_EQ(this->GetProp(results[2].vertex), 1); EXPECT_EQ(results[2].total_weight, 17); - EXPECT_EQ(GetProp(results[3].vertex), 2); + EXPECT_EQ(this->GetProp(results[3].vertex), 2); EXPECT_EQ(results[3].total_weight, 18); } } -TEST_F(QueryPlanExpandWeightedShortestPath, Where) { +TYPED_TEST(QueryPlanExpandWeightedShortestPath, Where) { { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, PropNe(filter_node, 2)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, this->PropNe(this->filter_node, 2)); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 1); + EXPECT_EQ(this->GetProp(results[0].vertex), 1); EXPECT_EQ(results[0].total_weight, 5); - EXPECT_EQ(GetProp(results[1].vertex), 4); + EXPECT_EQ(this->GetProp(results[1].vertex), 4); EXPECT_EQ(results[1].total_weight, 10); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 13); } { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, PropNe(filter_node, 1)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, this->PropNe(this->filter_node, 1)); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 3); + EXPECT_EQ(this->GetProp(results[1].vertex), 3); EXPECT_EQ(results[1].total_weight, 6); - EXPECT_EQ(GetProp(results[2].vertex), 4); + EXPECT_EQ(this->GetProp(results[2].vertex), 4); EXPECT_EQ(results[2].total_weight, 9); } } -TEST_F(QueryPlanExpandWeightedShortestPath, ExistingNode) { +TYPED_TEST(QueryPlanExpandWeightedShortestPath, ExistingNode) { auto ExpandPreceeding = [this](std::optional<int> preceeding_node_id) { // scan the nodes optionally filtering on property value - auto n0 = MakeScanAll(storage, symbol_table, "n0"); + auto n0 = MakeScanAll(this->storage, this->symbol_table, "n0"); if (preceeding_node_id) { - auto filter = - std::make_shared<Filter>(n0.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP(n0.node_->identifier_, prop), LITERAL(*preceeding_node_id))); + auto filter = std::make_shared<Filter>( + n0.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, + EQ(PROPERTY_LOOKUP(this->dba, n0.node_->identifier_, this->prop), LITERAL(*preceeding_node_id))); // inject the filter op into the ScanAllTuple. that way the filter // op can be passed into the ExpandWShortest function without too // much refactor n0.op_ = filter; } - return ExpandWShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true), std::nullopt, &n0); + return this->ExpandWShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true), std::nullopt, &n0); }; EXPECT_EQ(ExpandPreceeding(std::nullopt).size(), 20); { auto results = ExpandPreceeding(3); ASSERT_EQ(results.size(), 4); - for (int i = 0; i < 4; i++) EXPECT_EQ(GetProp(results[i].vertex), 3); + for (int i = 0; i < 4; i++) EXPECT_EQ(this->GetProp(results[i].vertex), 3); } } -TEST_F(QueryPlanExpandWeightedShortestPath, UpperBound) { +TYPED_TEST(QueryPlanExpandWeightedShortestPath, UpperBound) { { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, std::nullopt, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, std::nullopt, LITERAL(true)); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); } { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 2, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 2, LITERAL(true)); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 10); } { - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1, LITERAL(true)); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 4); + EXPECT_EQ(this->GetProp(results[2].vertex), 4); EXPECT_EQ(results[2].total_weight, 12); } { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(2)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(2)).HasValue()); + this->dba.AdvanceCommand(); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 3, LITERAL(true)); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 3, LITERAL(true)); ASSERT_EQ(results.size(), 5); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); - EXPECT_EQ(GetProp(results[4].vertex), 5); + EXPECT_EQ(this->GetProp(results[4].vertex), 5); EXPECT_EQ(results[4].total_weight, 12); } } -TEST_F(QueryPlanExpandWeightedShortestPath, NonNumericWeight) { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); +TYPED_TEST(QueryPlanExpandWeightedShortestPath, NonNumericWeight) { + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue("not a number")).HasValue()); - dba.AdvanceCommand(); - EXPECT_THROW(ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue("not a number")).HasValue()); + this->dba.AdvanceCommand(); + EXPECT_THROW(this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); } -TEST_F(QueryPlanExpandWeightedShortestPath, NegativeWeight) { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); +TYPED_TEST(QueryPlanExpandWeightedShortestPath, NegativeWeight) { + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(-10)).HasValue()); // negative weight - dba.AdvanceCommand(); - EXPECT_THROW(ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); + ASSERT_TRUE( + edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(-10)).HasValue()); // negative weight + this->dba.AdvanceCommand(); + EXPECT_THROW(this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); } -TEST_F(QueryPlanExpandWeightedShortestPath, NegativeUpperBound) { - EXPECT_THROW(ExpandWShortest(EdgeAtom::Direction::BOTH, -1, LITERAL(true)), QueryRuntimeException); +TYPED_TEST(QueryPlanExpandWeightedShortestPath, NegativeUpperBound) { + EXPECT_THROW(this->ExpandWShortest(EdgeAtom::Direction::BOTH, -1, LITERAL(true)), QueryRuntimeException); } #if MG_ENTERPRISE -TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { +TYPED_TEST(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { // All edge_types and labels allowed { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); EXPECT_EQ(results[0].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[0].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[0].path[0]), 3); EXPECT_EQ(results[0].total_weight, 3); EXPECT_EQ(results[1].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[1].path[0]), 5); + EXPECT_EQ(this->GetDoubleProp(results[1].path[0]), 5); EXPECT_EQ(results[1].total_weight, 5); EXPECT_EQ(results[2].path.size(), 2); - EXPECT_EQ(GetDoubleProp(results[2].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[2].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[1]), 3); EXPECT_EQ(results[2].total_weight, 6); EXPECT_EQ(results[3].path.size(), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[1]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[2]), 3); EXPECT_EQ(results[3].total_weight, 9); } @@ -2104,7 +2165,7 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2114,7 +2175,7 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2124,7 +2185,7 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("l0", memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2138,43 +2199,44 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("l4", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 4); user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::NOTHING); - auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(filtered_results.size(), 3); } // Deny edge type (created vertex 5 and edge vertex 4 to vertex 5) { - v.push_back(dba.InsertVertex()); - ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - ASSERT_TRUE(v.back().AddLabel(db.NameToLabel("l5")).HasValue()); - dba.AdvanceCommand(); - memgraph::storage::EdgeTypeId edge_type_filter = dba.NameToEdgeType("edge_type_filter"); - auto edge = dba.InsertEdge(&v[4], &v[5], edge_type_filter); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue()); - e.emplace(std::make_pair(4, 5), *edge); - dba.AdvanceCommand(); + this->v.push_back(this->dba.InsertVertex()); + ASSERT_TRUE(this->v.back().SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + ASSERT_TRUE(this->v.back().AddLabel(this->db->NameToLabel("l5")).HasValue()); + this->dba.AdvanceCommand(); + memgraph::storage::EdgeTypeId edge_type_filter = this->dba.NameToEdgeType("edge_type_filter"); + auto edge = this->dba.InsertEdge(&this->v[4], &this->v[5], edge_type_filter); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(1)).HasValue()); + this->e.emplace(std::make_pair(4, 5), *edge); + this->dba.AdvanceCommand(); memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 5); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_filter", memgraph::auth::FineGrainedPermission::NOTHING); - auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = this->ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(filtered_results.size(), 4); } } #endif /** A test fixture for all shortest paths expansion */ +template <typename StorageType> class QueryPlanExpandAllShortestPaths : public testing::Test { public: struct ResultType { @@ -2184,10 +2246,11 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { }; protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; - std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR("property"); + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; + std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); // make 5 vertices because we'll need to compare against them exactly @@ -2209,14 +2272,14 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { Symbol total_weight = symbol_table.CreateSymbol("total_weight", true); - void SetUp() { + void SetUp() override { memgraph::license::global_license_checker.EnableTesting(); for (int i = 0; i < 5; i++) { v.push_back(dba.InsertVertex()); ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(i)).HasValue()); auto label = fmt::format("l{}", i); - ASSERT_TRUE(v.back().AddLabel(db.NameToLabel(label)).HasValue()); + ASSERT_TRUE(v.back().AddLabel(db->NameToLabel(label)).HasValue()); } auto add_edge = [&](int from, int to, double weight) { @@ -2235,6 +2298,12 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { dba.AdvanceCommand(); } + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + // defines and performs an all shortest paths expansion with the given // params returns a vector of pairs. each pair is (vector-of-edges, // vertex) @@ -2246,7 +2315,7 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { auto last_op = n.op_; if (node_id) { last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(*node_id))); + EQ(PROPERTY_LOOKUP(dba, n.node_->identifier_, prop), LITERAL(*node_id))); } auto ident_e = IDENT("e"); @@ -2259,7 +2328,7 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { last_op, n.sym_, node_sym, edge_list_sym, EdgeAtom::Type::ALL_SHORTEST_PATHS, direction, std::vector<memgraph::storage::EdgeTypeId>{}, false, nullptr, max_depth ? LITERAL(max_depth.value()) : nullptr, existing_node_input != nullptr, ExpansionLambda{filter_edge, filter_node, where}, - ExpansionLambda{weight_edge, weight_node, PROPERTY_LOOKUP(ident_e, prop)}, total_weight); + ExpansionLambda{weight_edge, weight_node, PROPERTY_LOOKUP(dba, ident_e, prop)}, total_weight); Frame frame(symbol_table.max_position()); auto cursor = last_op->MakeCursor(memgraph::utils::NewDeleteResource()); @@ -2296,12 +2365,16 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { Expression *PropNe(Symbol symbol, int value) { auto ident = IDENT("inner_element"); ident->MapTo(symbol); - return NEQ(PROPERTY_LOOKUP(ident, prop), LITERAL(value)); + return NEQ(PROPERTY_LOOKUP(dba, ident, prop), LITERAL(value)); } }; -bool compareResultType(const QueryPlanExpandAllShortestPaths::ResultType &a, - const QueryPlanExpandAllShortestPaths::ResultType &b) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlanExpandAllShortestPaths, StorageTypes); + +template <typename StorageType> +bool compareResultType(const typename QueryPlanExpandAllShortestPaths<StorageType>::ResultType &a, + const typename QueryPlanExpandAllShortestPaths<StorageType>::ResultType &b) { return a.total_weight < b.total_weight; } @@ -2317,175 +2390,176 @@ bool compareResultType(const QueryPlanExpandAllShortestPaths::ResultType &a, // \->[2]->-[3]->/ // 3 3 3 -TEST_F(QueryPlanExpandAllShortestPaths, Basic) { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); - sort(results.begin(), results.end(), compareResultType); +TYPED_TEST(QueryPlanExpandAllShortestPaths, Basic) { + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); + sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 4); // check end nodes - EXPECT_EQ(GetProp(results[0].vertex), 2); - EXPECT_EQ(GetProp(results[1].vertex), 1); - EXPECT_EQ(GetProp(results[2].vertex), 3); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); // check paths and total weights EXPECT_EQ(results[0].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[0].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[0].path[0]), 3); EXPECT_EQ(results[0].total_weight, 3); EXPECT_EQ(results[1].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[1].path[0]), 5); + EXPECT_EQ(this->GetDoubleProp(results[1].path[0]), 5); EXPECT_EQ(results[1].total_weight, 5); EXPECT_EQ(results[2].path.size(), 2); - EXPECT_EQ(GetDoubleProp(results[2].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[2].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[2].path[1]), 3); EXPECT_EQ(results[2].total_weight, 6); EXPECT_EQ(results[3].path.size(), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[1]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[0]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[1]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[2]), 3); EXPECT_EQ(results[3].total_weight, 9); } -TEST_F(QueryPlanExpandAllShortestPaths, EdgeDirection) { +TYPED_TEST(QueryPlanExpandAllShortestPaths, EdgeDirection) { { - auto results = ExpandAllShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); - sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); + sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); } { - auto results = ExpandAllShortest(EdgeAtom::Direction::IN, 1000, LITERAL(true)); - sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::IN, 1000, LITERAL(true)); + sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 4); + EXPECT_EQ(this->GetProp(results[0].vertex), 4); EXPECT_EQ(results[0].total_weight, 12); - EXPECT_EQ(GetProp(results[1].vertex), 3); + EXPECT_EQ(this->GetProp(results[1].vertex), 3); EXPECT_EQ(results[1].total_weight, 15); - EXPECT_EQ(GetProp(results[2].vertex), 1); + EXPECT_EQ(this->GetProp(results[2].vertex), 1); EXPECT_EQ(results[2].total_weight, 17); - EXPECT_EQ(GetProp(results[3].vertex), 2); + EXPECT_EQ(this->GetProp(results[3].vertex), 2); EXPECT_EQ(results[3].total_weight, 18); } } -TEST_F(QueryPlanExpandAllShortestPaths, Where) { +TYPED_TEST(QueryPlanExpandAllShortestPaths, Where) { { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, PropNe(filter_node, 2)); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, this->PropNe(this->filter_node, 2)); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 1); + EXPECT_EQ(this->GetProp(results[0].vertex), 1); EXPECT_EQ(results[0].total_weight, 5); - EXPECT_EQ(GetProp(results[1].vertex), 4); + EXPECT_EQ(this->GetProp(results[1].vertex), 4); EXPECT_EQ(results[1].total_weight, 10); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 13); } { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, PropNe(filter_node, 1)); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, this->PropNe(this->filter_node, 1)); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 3); + EXPECT_EQ(this->GetProp(results[1].vertex), 3); EXPECT_EQ(results[1].total_weight, 6); - EXPECT_EQ(GetProp(results[2].vertex), 4); + EXPECT_EQ(this->GetProp(results[2].vertex), 4); EXPECT_EQ(results[2].total_weight, 9); } } -TEST_F(QueryPlanExpandAllShortestPaths, UpperBound) { +TYPED_TEST(QueryPlanExpandAllShortestPaths, UpperBound) { { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, std::nullopt, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, std::nullopt, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); } { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 2, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 2, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 4); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 10); } { - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 3); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 4); + EXPECT_EQ(this->GetProp(results[2].vertex), 4); EXPECT_EQ(results[2].total_weight, 12); } { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(2)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(2)).HasValue()); + this->dba.AdvanceCommand(); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 3, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 3, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 5); - EXPECT_EQ(GetProp(results[0].vertex), 2); + EXPECT_EQ(this->GetProp(results[0].vertex), 2); EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(GetProp(results[1].vertex), 1); + EXPECT_EQ(this->GetProp(results[1].vertex), 1); EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(GetProp(results[2].vertex), 3); + EXPECT_EQ(this->GetProp(results[2].vertex), 3); EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(GetProp(results[3].vertex), 4); + EXPECT_EQ(this->GetProp(results[3].vertex), 4); EXPECT_EQ(results[3].total_weight, 9); - EXPECT_EQ(GetProp(results[4].vertex), 5); + EXPECT_EQ(this->GetProp(results[4].vertex), 5); EXPECT_EQ(results[4].total_weight, 12); } } -TEST_F(QueryPlanExpandAllShortestPaths, NonNumericWeight) { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); +TYPED_TEST(QueryPlanExpandAllShortestPaths, NonNumericWeight) { + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue("not a number")).HasValue()); - dba.AdvanceCommand(); - EXPECT_THROW(ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue("not a number")).HasValue()); + this->dba.AdvanceCommand(); + EXPECT_THROW(this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); } -TEST_F(QueryPlanExpandAllShortestPaths, NegativeWeight) { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); +TYPED_TEST(QueryPlanExpandAllShortestPaths, NegativeWeight) { + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(-10)).HasValue()); // negative weight - dba.AdvanceCommand(); - EXPECT_THROW(ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); + ASSERT_TRUE( + edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(-10)).HasValue()); // negative weight + this->dba.AdvanceCommand(); + EXPECT_THROW(this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)), QueryRuntimeException); } -TEST_F(QueryPlanExpandAllShortestPaths, NegativeUpperBound) { - EXPECT_THROW(ExpandAllShortest(EdgeAtom::Direction::BOTH, -1, LITERAL(true)), QueryRuntimeException); +TYPED_TEST(QueryPlanExpandAllShortestPaths, NegativeUpperBound) { + EXPECT_THROW(this->ExpandAllShortest(EdgeAtom::Direction::BOTH, -1, LITERAL(true)), QueryRuntimeException); } // MultiplePaths testing on this graph: @@ -2497,65 +2571,65 @@ TEST_F(QueryPlanExpandAllShortestPaths, NegativeUpperBound) { // [2]-->--[3]-> // 3 3 -TEST_F(QueryPlanExpandAllShortestPaths, MultiplePaths) { - auto new_vertex = dba.InsertVertex(); - ASSERT_TRUE(new_vertex.SetProperty(prop.second, memgraph::storage::PropertyValue(6)).HasValue()); +TYPED_TEST(QueryPlanExpandAllShortestPaths, MultiplePaths) { + auto new_vertex = this->dba.InsertVertex(); + ASSERT_TRUE(new_vertex.SetProperty(this->prop.second, memgraph::storage::PropertyValue(6)).HasValue()); - auto edge = dba.InsertEdge(&v[4], &new_vertex, edge_type); + auto edge = this->dba.InsertEdge(&this->v[4], &new_vertex, this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(1)).HasValue()); + this->dba.AdvanceCommand(); - auto edge2 = dba.InsertEdge(&v[1], &new_vertex, edge_type); + auto edge2 = this->dba.InsertEdge(&this->v[1], &new_vertex, this->edge_type); ASSERT_TRUE(edge2.HasValue()); - ASSERT_TRUE(edge2->SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge2->SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + this->dba.AdvanceCommand(); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 6); - EXPECT_EQ(GetProp(results[4].vertex), 6); + EXPECT_EQ(this->GetProp(results[4].vertex), 6); EXPECT_EQ(results[4].total_weight, 10); - EXPECT_EQ(GetProp(results[5].vertex), 6); + EXPECT_EQ(this->GetProp(results[5].vertex), 6); EXPECT_EQ(results[5].total_weight, 10); } // Uses graph from Basic test, with double edge 2->-3 and 3->-4 -TEST_F(QueryPlanExpandAllShortestPaths, MultiEdge) { - auto edge = dba.InsertEdge(&v[2], &v[3], edge_type); +TYPED_TEST(QueryPlanExpandAllShortestPaths, MultiEdge) { + auto edge = this->dba.InsertEdge(&this->v[2], &this->v[3], this->edge_type); ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(3)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(3)).HasValue()); + this->dba.AdvanceCommand(); - auto edge2 = dba.InsertEdge(&v[3], &v[4], edge_type); + auto edge2 = this->dba.InsertEdge(&this->v[3], &this->v[4], this->edge_type); ASSERT_TRUE(edge2.HasValue()); - ASSERT_TRUE(edge2->SetProperty(prop.second, memgraph::storage::PropertyValue(3)).HasValue()); - dba.AdvanceCommand(); + ASSERT_TRUE(edge2->SetProperty(this->prop.second, memgraph::storage::PropertyValue(3)).HasValue()); + this->dba.AdvanceCommand(); - auto results = ExpandAllShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); - std::sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::OUT, 1000, LITERAL(true)); + std::sort(results.begin(), results.end(), compareResultType<TypeParam>); ASSERT_EQ(results.size(), 8); - EXPECT_EQ(GetProp(results[6].vertex), 4); + EXPECT_EQ(this->GetProp(results[6].vertex), 4); EXPECT_EQ(results[4].total_weight, 9); - EXPECT_EQ(GetProp(results[7].vertex), 4); + EXPECT_EQ(this->GetProp(results[7].vertex), 4); EXPECT_EQ(results[5].total_weight, 9); } #ifdef MG_ENTERPRISE -TEST_F(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { +TYPED_TEST(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { // All edge_types and labels allowed { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); - sort(results.begin(), results.end(), compareResultType); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); + sort(results.begin(), results.end(), compareResultType<TypeParam>); EXPECT_EQ(results[0].path.size(), 1); EXPECT_EQ(results[1].path.size(), 1); EXPECT_EQ(results[2].path.size(), 2); - EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); + EXPECT_EQ(this->GetDoubleProp(results[3].path[2]), 3); } // Denied all labels { @@ -2563,7 +2637,7 @@ TEST_F(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2572,7 +2646,7 @@ TEST_F(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("l0", memgraph::auth::FineGrainedPermission::NOTHING); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2587,49 +2661,47 @@ TEST_F(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("l4", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 4); user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::NOTHING); - auto filtered_results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(filtered_results.size(), 3); } // Deny edge type (created vertex 5 and edge vertex 4 to vertex 5) { - v.push_back(dba.InsertVertex()); - ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); - ASSERT_TRUE(v.back().AddLabel(db.NameToLabel("l5")).HasValue()); - dba.AdvanceCommand(); - memgraph::storage::EdgeTypeId edge_type_filter = dba.NameToEdgeType("edge_type_filter"); - auto edge = dba.InsertEdge(&v[4], &v[5], edge_type_filter); - ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue()); - e.emplace(std::make_pair(4, 5), *edge); - dba.AdvanceCommand(); + this->v.push_back(this->dba.InsertVertex()); + ASSERT_TRUE(this->v.back().SetProperty(this->prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + ASSERT_TRUE(this->v.back().AddLabel(this->db->NameToLabel("l5")).HasValue()); + this->dba.AdvanceCommand(); + memgraph::storage::EdgeTypeId edge_type_filter = this->dba.NameToEdgeType("edge_type_filter"); + auto edge = this->dba.InsertEdge(&this->v[4], &this->v[5], edge_type_filter); + ASSERT_TRUE(edge->SetProperty(this->prop.second, memgraph::storage::PropertyValue(1)).HasValue()); + this->e.emplace(std::make_pair(4, 5), *edge); + this->dba.AdvanceCommand(); memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 5); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_filter", memgraph::auth::FineGrainedPermission::NOTHING); - auto filtered_results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = this->ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(filtered_results.size(), 4); } } #endif -TEST(QueryPlan, ExpandOptional) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, ExpandOptional) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); - AstStorage storage; SymbolTable symbol_table; // graph (v2 {p: 2})<-[:T]-(v1 {p: 1})-[:T]->(v3 {p: 2}) @@ -2646,8 +2718,8 @@ TEST(QueryPlan, ExpandOptional) { dba.AdvanceCommand(); // MATCH (n) OPTIONAL MATCH (n)-[r]->(m) - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, nullptr, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, nullptr, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); auto optional = std::make_shared<plan::Optional>(n.op_, r_m.op_, std::vector<Symbol>{r_m.edge_sym_, r_m.node_sym_}); @@ -2656,7 +2728,7 @@ TEST(QueryPlan, ExpandOptional) { auto r_ne = NEXPR("r", IDENT("r")->MapTo(r_m.edge_sym_))->MapTo(symbol_table.CreateSymbol("r", true)); auto m_ne = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("m", true)); auto produce = MakeProduce(optional, n_ne, r_ne, m_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(4, results.size()); int v1_is_n_count = 0; @@ -2677,34 +2749,29 @@ TEST(QueryPlan, ExpandOptional) { EXPECT_EQ(2, v1_is_n_count); } -TEST(QueryPlan, OptionalMatchEmptyDB) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - - AstStorage storage; +TYPED_TEST(QueryPlan, OptionalMatchEmptyDB) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; // OPTIONAL MATCH (n) - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); // RETURN n auto n_ne = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto optional = std::make_shared<plan::Optional>(nullptr, n.op_, std::vector<Symbol>{n.sym_}); auto produce = MakeProduce(optional, n_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(1, results.size()); EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null); } -TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; // OPTIONAL MATCH (n) - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto optional = std::make_shared<plan::Optional>(nullptr, n.op_, std::vector<Symbol>{n.sym_}); // WITH n auto n_ne = NEXPR("n", IDENT("n")->MapTo(n.sym_)); @@ -2712,20 +2779,19 @@ TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) { n_ne->MapTo(with_n_sym); auto with = MakeProduce(optional, n_ne); // MATCH (n) -[r]-> (m) - auto r_m = MakeExpand(storage, symbol_table, with, with_n_sym, "r", EdgeAtom::Direction::OUT, {}, "m", false, + auto r_m = MakeExpand(this->storage, symbol_table, with, with_n_sym, "r", EdgeAtom::Direction::OUT, {}, "m", false, memgraph::storage::View::OLD); // RETURN m auto m_ne = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("m", true)); auto produce = MakeProduce(r_m.op_, m_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(0, results.size()); } -TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph with 2 connected, unlabeled nodes. auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -2734,14 +2800,13 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { dba.AdvanceCommand(); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); EXPECT_EQ(1, CountEdges(&dba, memgraph::storage::View::OLD)); - AstStorage storage; SymbolTable symbol_table; // OPTIONAL MATCH (n :missing) - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); auto label_missing = "missing"; - n.node_->labels_.emplace_back(storage.GetLabelIx(label_missing)); + n.node_->labels_.emplace_back(this->storage.GetLabelIx(label_missing)); - auto *filter_expr = storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); + auto *filter_expr = this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_); auto node_filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); auto optional = std::make_shared<plan::Optional>(nullptr, node_filter, std::vector<Symbol>{n.sym_}); // WITH n @@ -2750,7 +2815,7 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { n_ne->MapTo(with_n_sym); auto with = MakeProduce(optional, n_ne); // MATCH (m) -[r]-> (n) - auto m = MakeScanAll(storage, symbol_table, "m", with); + auto m = MakeScanAll(this->storage, symbol_table, "m", with); auto edge_direction = EdgeAtom::Direction::OUT; auto edge = EDGE("r", edge_direction); auto edge_sym = symbol_table.CreateSymbol("r", true); @@ -2763,15 +2828,14 @@ TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { // RETURN m auto m_ne = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("m", true)); auto produce = MakeProduce(expand, m_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(0, results.size()); } -TEST(QueryPlan, ExpandExistingNode) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, ExpandExistingNode) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // make a graph (v1)->(v2) that // has a recursive edge (v1)->(v1) @@ -2782,13 +2846,12 @@ TEST(QueryPlan, ExpandExistingNode) { ASSERT_TRUE(dba.InsertEdge(&v1, &v2, edge_type).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; auto test_existing = [&](bool with_existing, int expected_result_count) { - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_n = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "n", with_existing, - memgraph::storage::View::OLD); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_n = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "n", + with_existing, memgraph::storage::View::OLD); if (with_existing) r_n.op_ = std::make_shared<Expand>(n.op_, n.sym_, n.sym_, r_n.edge_sym_, r_n.edge_->direction_, std::vector<memgraph::storage::EdgeTypeId>{}, with_existing, @@ -2797,7 +2860,7 @@ TEST(QueryPlan, ExpandExistingNode) { // make a named expression and a produce auto output = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(r_n.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), expected_result_count); }; @@ -2806,31 +2869,28 @@ TEST(QueryPlan, ExpandExistingNode) { test_existing(false, 2); } -TEST(QueryPlan, ExpandBothCycleEdgeCase) { +TYPED_TEST(QueryPlan, ExpandBothCycleEdgeCase) { // we're testing that expanding on BOTH // does only one expansion for a cycle - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); ASSERT_TRUE(dba.InsertEdge(&v, &v, dba.NameToEdgeType("et")).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_ = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "_", false, + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_ = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "_", false, memgraph::storage::View::OLD); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(1, PullAll(*r_.op_, &context)); } -TEST(QueryPlan, EdgeFilter) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, EdgeFilter) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // make an N-star expanding from (v1) // where only one edge will qualify @@ -2840,7 +2900,7 @@ TEST(QueryPlan, EdgeFilter) { for (int j = 0; j < 2; ++j) edge_types.push_back(dba.NameToEdgeType("et" + std::to_string(j))); std::vector<memgraph::query::VertexAccessor> vertices; for (int i = 0; i < 7; ++i) vertices.push_back(dba.InsertVertex()); - auto prop = PROPERTY_PAIR("property"); + auto prop = PROPERTY_PAIR(dba, "property"); std::vector<memgraph::query::EdgeAccessor> edges; for (int i = 0; i < 6; ++i) { edges.push_back(*dba.InsertEdge(&vertices[0], &vertices[i + 1], edge_types[i % 2])); @@ -2857,27 +2917,26 @@ TEST(QueryPlan, EdgeFilter) { } dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; auto test_filter = [&]() { // define an operator tree for query // MATCH (n)-[r :et0 {property: 42}]->(m) RETURN m - auto n = MakeScanAll(storage, symbol_table, "n"); + auto n = MakeScanAll(this->storage, symbol_table, "n"); const auto &edge_type = edge_types[0]; - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {edge_type}, "m", false, - memgraph::storage::View::OLD); - r_m.edge_->edge_types_.push_back(storage.GetEdgeTypeIx(dba.EdgeTypeToName(edge_type))); - std::get<0>(r_m.edge_->properties_)[storage.GetPropertyIx(prop.first)] = LITERAL(42); - auto *filter_expr = EQ(PROPERTY_LOOKUP(r_m.edge_->identifier_, prop), LITERAL(42)); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {edge_type}, "m", + false, memgraph::storage::View::OLD); + r_m.edge_->edge_types_.push_back(this->storage.GetEdgeTypeIx(dba.EdgeTypeToName(edge_type))); + std::get<0>(r_m.edge_->properties_)[this->storage.GetPropertyIx(prop.first)] = LITERAL(42); + auto *filter_expr = EQ(PROPERTY_LOOKUP(dba, r_m.edge_->identifier_, prop), LITERAL(42)); auto edge_filter = std::make_shared<Filter>(r_m.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); // make a named expression and a produce auto output = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(edge_filter, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); return PullAll(*produce, &context); }; @@ -2889,10 +2948,9 @@ TEST(QueryPlan, EdgeFilter) { EXPECT_EQ(3, test_filter()); } -TEST(QueryPlan, EdgeFilterMultipleTypes) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, EdgeFilterMultipleTypes) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -2904,53 +2962,49 @@ TEST(QueryPlan, EdgeFilterMultipleTypes) { ASSERT_TRUE(dba.InsertEdge(&v1, &v2, type_3).HasValue()); dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; // make a scan all - auto n = MakeScanAll(storage, symbol_table, "n"); - auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {type_1, type_2}, "m", - false, memgraph::storage::View::OLD); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto r_m = MakeExpand(this->storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {type_1, type_2}, + "m", false, memgraph::storage::View::OLD); // make a named expression and a produce auto output = NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(r_m.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } -TEST(QueryPlan, Filter) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, Filter) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // add a 6 nodes with property 'prop', 2 have true as value - auto property = PROPERTY_PAIR("property"); + auto property = PROPERTY_PAIR(dba, "property"); for (int i = 0; i < 6; ++i) ASSERT_TRUE( dba.InsertVertex().SetProperty(property.second, memgraph::storage::PropertyValue(i % 3 == 0)).HasValue()); dba.InsertVertex(); // prop not set, gives NULL dba.AdvanceCommand(); - AstStorage storage; SymbolTable symbol_table; - auto n = MakeScanAll(storage, symbol_table, "n"); - auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), property); + auto n = MakeScanAll(this->storage, symbol_table, "n"); + auto e = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(n.sym_), property); auto f = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, e); auto output = NEXPR("x", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(f, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(CollectProduce(*produce, &context).size(), 2); } -TEST(QueryPlan, EdgeUniquenessFilter) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, EdgeUniquenessFilter) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // make a graph that has (v1)->(v2) and a recursive edge (v1)->(v1) auto v1 = dba.InsertVertex(); @@ -2961,19 +3015,18 @@ TEST(QueryPlan, EdgeUniquenessFilter) { dba.AdvanceCommand(); auto check_expand_results = [&](bool edge_uniqueness) { - AstStorage storage; SymbolTable symbol_table; - auto n1 = MakeScanAll(storage, symbol_table, "n1"); - auto r1_n2 = MakeExpand(storage, symbol_table, n1.op_, n1.sym_, "r1", EdgeAtom::Direction::OUT, {}, "n2", false, - memgraph::storage::View::OLD); - std::shared_ptr<LogicalOperator> last_op = r1_n2.op_; - auto r2_n3 = MakeExpand(storage, symbol_table, last_op, r1_n2.node_sym_, "r2", EdgeAtom::Direction::OUT, {}, "n3", + auto n1 = MakeScanAll(this->storage, symbol_table, "n1"); + auto r1_n2 = MakeExpand(this->storage, symbol_table, n1.op_, n1.sym_, "r1", EdgeAtom::Direction::OUT, {}, "n2", false, memgraph::storage::View::OLD); + std::shared_ptr<LogicalOperator> last_op = r1_n2.op_; + auto r2_n3 = MakeExpand(this->storage, symbol_table, last_op, r1_n2.node_sym_, "r2", EdgeAtom::Direction::OUT, {}, + "n3", false, memgraph::storage::View::OLD); last_op = r2_n3.op_; if (edge_uniqueness) last_op = std::make_shared<EdgeUniquenessFilter>(last_op, r2_n3.edge_sym_, std::vector<Symbol>{r1_n2.edge_sym_}); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); return PullAll(*last_op, &context); }; @@ -2981,14 +3034,12 @@ TEST(QueryPlan, EdgeUniquenessFilter) { EXPECT_EQ(1, check_expand_results(true)); } -TEST(QueryPlan, Distinct) { +TYPED_TEST(QueryPlan, Distinct) { // test queries like // UNWIND [1, 2, 3, 3] AS x RETURN DISTINCT x - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto check_distinct = [&](const std::vector<TypedValue> input, const std::vector<TypedValue> output, @@ -3005,7 +3056,7 @@ TEST(QueryPlan, Distinct) { auto x_ne = NEXPR("x", x_expr); x_ne->MapTo(symbol_table.CreateSymbol("x_ne", true)); auto produce = MakeProduce(distinct, x_ne); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(output.size(), results.size()); auto output_it = output.begin(); @@ -3028,12 +3079,11 @@ TEST(QueryPlan, Distinct) { {TypedValue(3), TypedValue("two"), TypedValue(), TypedValue(true), TypedValue(false), TypedValue("TWO")}, false); } -TEST(QueryPlan, ScanAllByLabel) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - [[maybe_unused]] auto _ = db.CreateIndex(label); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(QueryPlan, ScanAllByLabel) { + auto label = this->db->NameToLabel("label"); + [[maybe_unused]] auto _ = this->db->CreateIndex(label); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Add a vertex with a label and one without. auto labeled_vertex = dba.InsertVertex(); ASSERT_TRUE(labeled_vertex.AddLabel(label).HasValue()); @@ -3041,25 +3091,24 @@ TEST(QueryPlan, ScanAllByLabel) { dba.AdvanceCommand(); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label) - AstStorage storage; SymbolTable symbol_table; - auto scan_all_by_label = MakeScanAllByLabel(storage, symbol_table, "n", label); + auto scan_all_by_label = MakeScanAllByLabel(this->storage, symbol_table, "n", label); // RETURN n auto output = NEXPR("n", IDENT("n")->MapTo(scan_all_by_label.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto produce = MakeProduce(scan_all_by_label.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(results.size(), 1); auto result_row = results[0]; ASSERT_EQ(result_row.size(), 1); - EXPECT_EQ(result_row[0].ValueVertex(), labeled_vertex); + auto result_vertex = result_row[0].ValueVertex(); + EXPECT_EQ(result_vertex.Gid(), labeled_vertex.Gid()); } -TEST(QueryPlan, ScanAllByLabelProperty) { - memgraph::storage::Storage db; +TYPED_TEST(QueryPlan, ScanAllByLabelProperty) { // Add 5 vertices with same label, but with different property values. - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); // vertex property values that will be stored into the DB std::vector<memgraph::storage::PropertyValue> values{ memgraph::storage::PropertyValue(true), @@ -3080,8 +3129,8 @@ TEST(QueryPlan, ScanAllByLabelProperty) { memgraph::storage::PropertyValue( std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(2)})}; { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); for (const auto &value : values) { auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); @@ -3089,36 +3138,49 @@ TEST(QueryPlan, ScanAllByLabelProperty) { } ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); ASSERT_EQ(14, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); auto run_scan_all = [&](const TypedValue &lower, Bound::Type lower_type, const TypedValue &upper, Bound::Type upper_type) { - AstStorage storage; SymbolTable symbol_table; auto scan_all = - MakeScanAllByLabelPropertyRange(storage, symbol_table, "n", label, prop, "prop", + MakeScanAllByLabelPropertyRange(this->storage, symbol_table, "n", label, prop, "prop", Bound{LITERAL(lower), lower_type}, Bound{LITERAL(upper), upper_type}); // RETURN n auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto produce = MakeProduce(scan_all.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); return CollectProduce(*produce, &context); }; + auto check_no_order = [&](auto results, auto expected) -> bool { + for (size_t i = 0; i < expected.size(); i++) { + bool local_check = false; + for (size_t j = 0; j < results.size(); j++) { + bool local_equal = + (TypedValue(*results[j][0].ValueVertex().GetProperty(memgraph::storage::View::OLD, prop)) == expected[i]) + .ValueBool(); + if (local_equal) { + local_check = true; + break; + } + } + if (!local_check) { + return false; + } + } + return true; + }; + auto check = [&](TypedValue lower, Bound::Type lower_type, TypedValue upper, Bound::Type upper_type, const std::vector<TypedValue> &expected) { auto results = run_scan_all(lower, lower_type, upper, upper_type); ASSERT_EQ(results.size(), expected.size()); - for (size_t i = 0; i < expected.size(); i++) { - TypedValue equal = - TypedValue(*results[i][0].ValueVertex().GetProperty(memgraph::storage::View::OLD, prop)) == expected[i]; - ASSERT_EQ(equal.type(), TypedValue::Type::Bool); - EXPECT_TRUE(equal.ValueBool()); - } + ASSERT_TRUE(check_no_order(results, expected)); }; // normal ranges that return something @@ -3166,15 +3228,14 @@ TEST(QueryPlan, ScanAllByLabelProperty) { QueryRuntimeException); } -TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { - memgraph::storage::Storage db; +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { // Add 2 vertices with same label, but with property values that cannot be // compared. On the other hand, equality works fine. - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto number_vertex = dba.InsertVertex(); ASSERT_TRUE(number_vertex.AddLabel(label).HasValue()); ASSERT_TRUE(number_vertex.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue()); @@ -3183,19 +3244,18 @@ TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { ASSERT_TRUE(string_vertex.SetProperty(prop, memgraph::storage::PropertyValue("string")).HasValue()); ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label {prop: 42}) - AstStorage storage; SymbolTable symbol_table; - auto scan_all = MakeScanAllByLabelPropertyValue(storage, symbol_table, "n", label, prop, "prop", LITERAL(42)); + auto scan_all = MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", LITERAL(42)); // RETURN n auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto produce = MakeProduce(scan_all.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); ASSERT_EQ(results.size(), 1); const auto &row = results[0]; @@ -3206,13 +3266,12 @@ TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { EXPECT_TRUE(eq(value, TypedValue(42))); } -TEST(QueryPlan, ScanAllByLabelPropertyValueError) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyValueError) { + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); for (int i = 0; i < 2; ++i) { auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); @@ -3220,30 +3279,28 @@ TEST(QueryPlan, ScanAllByLabelPropertyValueError) { } ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (m), (n :label {prop: m}) - AstStorage storage; SymbolTable symbol_table; - auto scan_all = MakeScanAll(storage, symbol_table, "m"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "m"); auto *ident_m = IDENT("m"); ident_m->MapTo(scan_all.sym_); auto scan_index = - MakeScanAllByLabelPropertyValue(storage, symbol_table, "n", label, prop, "prop", ident_m, scan_all.op_); - auto context = MakeContext(storage, symbol_table, &dba); + MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", ident_m, scan_all.op_); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*scan_index.op_, &context), QueryRuntimeException); } -TEST(QueryPlan, ScanAllByLabelPropertyRangeError) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeError) { + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); for (int i = 0; i < 2; ++i) { auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); @@ -3251,52 +3308,51 @@ TEST(QueryPlan, ScanAllByLabelPropertyRangeError) { } ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (m), (n :label {prop: m}) - AstStorage storage; SymbolTable symbol_table; - auto scan_all = MakeScanAll(storage, symbol_table, "m"); + auto scan_all = MakeScanAll(this->storage, symbol_table, "m"); auto *ident_m = IDENT("m"); ident_m->MapTo(scan_all.sym_); { // Lower bound isn't property value auto scan_index = - MakeScanAllByLabelPropertyRange(storage, symbol_table, "n", label, prop, "prop", + MakeScanAllByLabelPropertyRange(this->storage, symbol_table, "n", label, prop, "prop", Bound{ident_m, Bound::Type::INCLUSIVE}, std::nullopt, scan_all.op_); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*scan_index.op_, &context), QueryRuntimeException); } { // Upper bound isn't property value - auto scan_index = MakeScanAllByLabelPropertyRange(storage, symbol_table, "n", label, prop, "prop", std::nullopt, - Bound{ident_m, Bound::Type::INCLUSIVE}, scan_all.op_); - auto context = MakeContext(storage, symbol_table, &dba); + auto scan_index = + MakeScanAllByLabelPropertyRange(this->storage, symbol_table, "n", label, prop, "prop", std::nullopt, + Bound{ident_m, Bound::Type::INCLUSIVE}, scan_all.op_); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*scan_index.op_, &context), QueryRuntimeException); } { // Both bounds aren't property value - auto scan_index = MakeScanAllByLabelPropertyRange(storage, symbol_table, "n", label, prop, "prop", + auto scan_index = MakeScanAllByLabelPropertyRange(this->storage, symbol_table, "n", label, prop, "prop", Bound{ident_m, Bound::Type::INCLUSIVE}, Bound{ident_m, Bound::Type::INCLUSIVE}, scan_all.op_); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_THROW(PullAll(*scan_index.op_, &context), QueryRuntimeException); } } -TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) { - memgraph::storage::Storage db; +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) { // Add 2 vertices with the same label, but one has a property value while // the other does not. Checking if the value is equal to null, should // yield no results. - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); auto vertex_with_prop = dba.InsertVertex(); @@ -3304,34 +3360,32 @@ TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) { ASSERT_TRUE(vertex_with_prop.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue()); ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label {prop: 42}) - AstStorage storage; SymbolTable symbol_table; auto scan_all = - MakeScanAllByLabelPropertyValue(storage, symbol_table, "n", label, prop, "prop", LITERAL(TypedValue())); + MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", LITERAL(TypedValue())); // RETURN n auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto produce = MakeProduce(scan_all.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 0); } -TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) { - memgraph::storage::Storage db; +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) { // Add 2 vertices with the same label, but one has a property value while // the other does not. Checking if the value is between nulls, should // yield no results. - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); auto vertex_with_prop = dba.InsertVertex(); @@ -3339,44 +3393,41 @@ TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) { ASSERT_TRUE(vertex_with_prop.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue()); ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label) WHERE null <= n.prop < null - AstStorage storage; SymbolTable symbol_table; - auto scan_all = MakeScanAllByLabelPropertyRange(storage, symbol_table, "n", label, prop, "prop", + auto scan_all = MakeScanAllByLabelPropertyRange(this->storage, symbol_table, "n", label, prop, "prop", Bound{LITERAL(TypedValue()), Bound::Type::INCLUSIVE}, Bound{LITERAL(TypedValue()), Bound::Type::EXCLUSIVE}); // RETURN n auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("n", true)); auto produce = MakeProduce(scan_all.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 0); } -TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); +TYPED_TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) { + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); ASSERT_TRUE(v.AddLabel(label).HasValue()); ASSERT_TRUE(v.SetProperty(prop, memgraph::storage::PropertyValue(2)).HasValue()); ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); - AstStorage storage; SymbolTable symbol_table; // UNWIND [1, 2, 3] as x @@ -3387,24 +3438,24 @@ TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) { x_expr->MapTo(x); // MATCH (n :label {prop: x}) - auto scan_all = MakeScanAllByLabelPropertyValue(storage, symbol_table, "n", label, prop, "prop", x_expr, unwind); + auto scan_all = + MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", x_expr, unwind); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(PullAll(*scan_all.op_, &context), 1); } -TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - auto prop = db.NameToProperty("prop"); +TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { + auto label = this->db->NameToLabel("label"); + auto prop = this->db->NameToProperty("prop"); // Insert vertices const int vertex_count = 300, vertex_prop_count = 50; const int prop_value1 = 42, prop_value2 = 69; for (int i = 0; i < vertex_count; ++i) { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); ASSERT_TRUE(v.AddLabel(label).HasValue()); ASSERT_TRUE(v.SetProperty(prop, memgraph::storage::PropertyValue(i < vertex_prop_count ? prop_value1 : prop_value2)) @@ -3412,44 +3463,42 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { ASSERT_FALSE(dba.Commit().HasError()); } - [[maybe_unused]] auto _ = db.CreateIndex(label, prop); + [[maybe_unused]] auto _ = this->db->CreateIndex(label, prop); // Make sure there are `vertex_count` vertices { - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(vertex_count, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); } // Make sure there are `vertex_prop_count` results when using index - auto count_with_index = [&db, &label, &prop](int prop_value, int prop_count) { - AstStorage storage; + auto count_with_index = [this, &label, &prop](int prop_value, int prop_count) { SymbolTable symbol_table; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto scan_all_by_label_property_value = - MakeScanAllByLabelPropertyValue(storage, symbol_table, "n", label, prop, "prop", LITERAL(prop_value)); + MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", LITERAL(prop_value)); auto output = NEXPR("n", IDENT("n")->MapTo(scan_all_by_label_property_value.sym_)) ->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(scan_all_by_label_property_value.op_, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(PullAll(*produce, &context), prop_count); }; // Make sure there are `vertex_count` results when using scan all - auto count_with_scan_all = [&db, &prop](int prop_value, int prop_count) { - AstStorage storage; + auto count_with_scan_all = [this, &prop](int prop_value, int prop_count) { SymbolTable symbol_table; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - auto scan_all = MakeScanAll(storage, symbol_table, "n"); - auto e = PROPERTY_LOOKUP(IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop)); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); + auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); + auto e = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop)); auto filter = std::make_shared<Filter>(scan_all.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, EQ(e, LITERAL(prop_value))); auto output = NEXPR("n", IDENT("n")->MapTo(scan_all.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); auto produce = MakeProduce(filter, output); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, symbol_table, &dba); EXPECT_EQ(PullAll(*produce, &context), prop_count); }; @@ -3460,24 +3509,26 @@ TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { count_with_scan_all(prop_value2, vertex_count - vertex_prop_count); } +template <typename StorageType> class ExistsFixture : public testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; - std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR("property"); + std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::query::VertexAccessor v1{dba.InsertVertex()}; memgraph::query::VertexAccessor v2{dba.InsertVertex()}; - memgraph::storage::EdgeTypeId edge_type{db.NameToEdgeType("Edge")}; + memgraph::storage::EdgeTypeId edge_type{db->NameToEdgeType("Edge")}; memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type)}; memgraph::query::VertexAccessor v3{dba.InsertVertex()}; memgraph::query::VertexAccessor v4{dba.InsertVertex()}; - memgraph::storage::EdgeTypeId edge_type_unknown{db.NameToEdgeType("Other")}; + memgraph::storage::EdgeTypeId edge_type_unknown{db->NameToEdgeType("Other")}; memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v3, &v4, edge_type_unknown)}; void SetUp() override { @@ -3498,6 +3549,12 @@ class ExistsFixture : public testing::Test { dba.AdvanceCommand(); } + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + int TestExists(std::string match_label, EdgeAtom::Direction direction, std::vector<memgraph::storage::EdgeTypeId> edge_types, std::optional<std::string> destination_label = std::nullopt, @@ -3505,7 +3562,7 @@ class ExistsFixture : public testing::Test { std::optional<int64_t> edge_prop = std::nullopt) { std::vector<std::string> edge_type_names; for (const auto &type : edge_types) { - edge_type_names.emplace_back(db.EdgeTypeToName(type)); + edge_type_names.emplace_back(db->EdgeTypeToName(type)); } auto *source_node = NODE("n"); @@ -3544,13 +3601,13 @@ class ExistsFixture : public testing::Test { if (destination_prop.has_value()) { auto prop_expr = static_cast<Expression *>( - EQ(PROPERTY_LOOKUP(destination_node->identifier_, prop), LITERAL(destination_prop.value()))); + EQ(PROPERTY_LOOKUP(dba, destination_node->identifier_, prop), LITERAL(destination_prop.value()))); filter_expr = filter_expr ? AND(filter_expr, prop_expr) : prop_expr; } if (edge_prop.has_value()) { auto prop_expr = static_cast<Expression *>( - EQ(PROPERTY_LOOKUP(expansion_edge->identifier_, prop), LITERAL(edge_prop.value()))); + EQ(PROPERTY_LOOKUP(dba, expansion_edge->identifier_, prop), LITERAL(edge_prop.value()))); filter_expr = filter_expr ? AND(filter_expr, prop_expr) : prop_expr; } @@ -3579,12 +3636,12 @@ class ExistsFixture : public testing::Test { std::vector<memgraph::storage::EdgeTypeId> second_edge_type, bool or_flag = false) { std::vector<std::string> first_edge_type_names; for (const auto &type : first_edge_type) { - first_edge_type_names.emplace_back(db.EdgeTypeToName(type)); + first_edge_type_names.emplace_back(db->EdgeTypeToName(type)); } std::vector<std::string> second_edge_type_names; for (const auto &type : second_edge_type) { - second_edge_type_names.emplace_back(db.EdgeTypeToName(type)); + second_edge_type_names.emplace_back(db->EdgeTypeToName(type)); } auto *source_node = NODE("n"); @@ -3645,51 +3702,56 @@ class ExistsFixture : public testing::Test { } }; -TEST_F(ExistsFixture, BasicExists) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ExistsFixture, StorageTypes); + +TYPED_TEST(ExistsFixture, BasicExists) { std::vector<memgraph::storage::EdgeTypeId> known_edge_types; - known_edge_types.push_back(edge_type); + known_edge_types.push_back(this->edge_type); std::vector<memgraph::storage::EdgeTypeId> unknown_edge_types; - unknown_edge_types.push_back(edge_type_unknown); + unknown_edge_types.push_back(this->edge_type_unknown); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::OUT, {})); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {})); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::IN, {})); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::OUT, known_edge_types)); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::OUT, unknown_edge_types)); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::OUT, {})); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::BOTH, {})); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::IN, {})); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::OUT, known_edge_types)); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::OUT, unknown_edge_types)); } -TEST_F(ExistsFixture, ExistsWithFiltering) { - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2")); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l3")); +TYPED_TEST(ExistsFixture, ExistsWithFiltering) { + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2")); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l3")); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2)); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1)); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2)); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1)); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 1)); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 2)); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 1)); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", std::nullopt, 2)); - EXPECT_EQ(1, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2, 1)); - EXPECT_EQ(0, TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1, 1)); + EXPECT_EQ(1, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 2, 1)); + EXPECT_EQ(0, this->TestExists("l1", EdgeAtom::Direction::BOTH, {}, "l2", 1, 1)); } -TEST_F(ExistsFixture, DoubleFilters) { - EXPECT_EQ(1, TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, true)); - EXPECT_EQ(1, TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, false)); +TYPED_TEST(ExistsFixture, DoubleFilters) { + EXPECT_EQ(1, this->TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, true)); + EXPECT_EQ(1, this->TestDoubleExists("l1", EdgeAtom::Direction::BOTH, {}, {}, false)); } +template <typename StorageType> class SubqueriesFeature : public testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; - std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR("property"); + std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::query::VertexAccessor v1{dba.InsertVertex()}; memgraph::query::VertexAccessor v2{dba.InsertVertex()}; - memgraph::storage::EdgeTypeId edge_type{db.NameToEdgeType("Edge")}; + memgraph::storage::EdgeTypeId edge_type{db->NameToEdgeType("Edge")}; memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type)}; void SetUp() override { @@ -3705,107 +3767,127 @@ class SubqueriesFeature : public testing::Test { dba.AdvanceCommand(); } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } }; -TEST_F(SubqueriesFeature, BasicCartesian) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(SubqueriesFeature, StorageTypes); + +TYPED_TEST(SubqueriesFeature, BasicCartesian) { // MATCH (n) CALL { MATCH (m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto m = MakeScanAll(this->storage, this->symbol_table, "m"); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); auto produce_subquery = MakeProduce(m.op_, return_m); auto apply = std::make_shared<Apply>(n.op_, produce_subquery, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 4); } -TEST_F(SubqueriesFeature, BasicCartesianWithFilter) { +TYPED_TEST(SubqueriesFeature, BasicCartesianWithFilter) { // MATCH (n) WHERE n.prop = 2 CALL { MATCH (m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), - EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(2))); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto *filter_expr = AND(this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), + EQ(PROPERTY_LOOKUP(this->dba, n.node_->identifier_, this->prop), LITERAL(2))); auto filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto m = MakeScanAll(this->storage, this->symbol_table, "m"); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); auto produce_subquery = MakeProduce(m.op_, return_m); auto apply = std::make_shared<Apply>(filter, produce_subquery, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } -TEST_F(SubqueriesFeature, BasicCartesianWithFilterInsideSubquery) { +TYPED_TEST(SubqueriesFeature, BasicCartesianWithFilterInsideSubquery) { // MATCH (n) CALL { MATCH (m) WHERE m.prop = 2 RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), - EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(2))); + auto m = MakeScanAll(this->storage, this->symbol_table, "m"); + auto *filter_expr = AND(this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), + EQ(PROPERTY_LOOKUP(this->dba, n.node_->identifier_, this->prop), LITERAL(2))); auto filter = std::make_shared<Filter>(m.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); auto produce_subquery = MakeProduce(filter, return_m); auto apply = std::make_shared<Apply>(n.op_, produce_subquery, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } -TEST_F(SubqueriesFeature, BasicCartesianWithFilterNoResults) { +TYPED_TEST(SubqueriesFeature, BasicCartesianWithFilterNoResults) { // MATCH (n) WHERE n.prop = 3 CALL { MATCH (m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto *filter_expr = AND(storage.Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), - EQ(PROPERTY_LOOKUP(n.node_->identifier_, prop), LITERAL(3))); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto *filter_expr = AND(this->storage.template Create<LabelsTest>(n.node_->identifier_, n.node_->labels_), + EQ(PROPERTY_LOOKUP(this->dba, n.node_->identifier_, this->prop), LITERAL(3))); auto filter = std::make_shared<Filter>(n.op_, std::vector<std::shared_ptr<LogicalOperator>>{}, filter_expr); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto m = MakeScanAll(this->storage, this->symbol_table, "m"); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); auto produce_subquery = MakeProduce(m.op_, return_m); auto apply = std::make_shared<Apply>(filter, produce_subquery, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 0); } -TEST_F(SubqueriesFeature, SubqueryInsideSubqueryCartesian) { +TYPED_TEST(SubqueriesFeature, SubqueryInsideSubqueryCartesian) { // MATCH (n) CALL { MATCH (m) CALL { MATCH (o) RETURN o} RETURN m, o } RETURN n, m, o - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m = MakeScanAll(storage, symbol_table, "m"); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto m = MakeScanAll(this->storage, this->symbol_table, "m"); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); - auto o = MakeScanAll(storage, symbol_table, "o"); - auto return_o = NEXPR("o", IDENT("o")->MapTo(o.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_3", true)); + auto o = MakeScanAll(this->storage, this->symbol_table, "o"); + auto return_o = + NEXPR("o", IDENT("o")->MapTo(o.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_3", true)); auto produce_nested_subquery = MakeProduce(o.op_, return_o); auto inner_apply = std::make_shared<Apply>(m.op_, produce_nested_subquery, true); @@ -3814,126 +3896,132 @@ TEST_F(SubqueriesFeature, SubqueryInsideSubqueryCartesian) { auto outer_apply = std::make_shared<Apply>(n.op_, produce_subquery, true); auto produce = MakeProduce(outer_apply, return_n, return_m, return_o); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 8); } -TEST_F(SubqueriesFeature, UnitSubquery) { +TYPED_TEST(SubqueriesFeature, UnitSubquery) { // CALL { MATCH (m) RETURN m } RETURN m auto once = std::make_shared<Once>(); - auto o = MakeScanAll(storage, symbol_table, "o"); - auto return_o = NEXPR("o", IDENT("o")->MapTo(o.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_3", true)); + auto o = MakeScanAll(this->storage, this->symbol_table, "o"); + auto return_o = + NEXPR("o", IDENT("o")->MapTo(o.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_3", true)); auto produce_subquery = MakeProduce(o.op_, return_o); auto apply = std::make_shared<Apply>(once, produce_subquery, true); auto produce = MakeProduce(apply, return_o); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } -TEST_F(SubqueriesFeature, SubqueryWithBoundedSymbol) { +TYPED_TEST(SubqueriesFeature, SubqueryWithBoundedSymbol) { // MATCH (n) CALL { WITH n MATCH (n)-[]->(m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto once = std::make_shared<Once>(); auto produce_with = MakeProduce(once, return_n); - auto expand = MakeExpand(storage, symbol_table, produce_with, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false, - memgraph::storage::View::OLD); - auto return_m = - NEXPR("m", IDENT("m")->MapTo(expand.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_3", true)); + auto expand = MakeExpand(this->storage, this->symbol_table, produce_with, n.sym_, "r", EdgeAtom::Direction::OUT, {}, + "m", false, memgraph::storage::View::OLD); + auto return_m = NEXPR("m", IDENT("m")->MapTo(expand.node_sym_)) + ->MapTo(this->symbol_table.CreateSymbol("named_expression_3", true)); auto produce_subquery = MakeProduce(expand.op_, return_m); auto apply = std::make_shared<Apply>(n.op_, produce_subquery, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 1); } -TEST_F(SubqueriesFeature, SubqueryWithUnionAll) { +TYPED_TEST(SubqueriesFeature, SubqueryWithUnionAll) { // MATCH (n) CALL { MATCH (m) RETURN m UNION ALL MATCH (m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m1 = MakeScanAll(storage, symbol_table, "m"); - auto return_m = NEXPR("m", IDENT("m")->MapTo(m1.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_2", true)); + auto m1 = MakeScanAll(this->storage, this->symbol_table, "m"); + auto return_m = + NEXPR("m", IDENT("m")->MapTo(m1.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_2", true)); auto produce_left_union_subquery = MakeProduce(m1.op_, return_m); - auto m2 = MakeScanAll(storage, symbol_table, "m"); + auto m2 = MakeScanAll(this->storage, this->symbol_table, "m"); auto produce_right_union_subquery = MakeProduce(m2.op_, return_m); auto union_operator = std::make_shared<Union>(produce_left_union_subquery, produce_right_union_subquery, std::vector<Symbol>{m1.sym_}, - produce_left_union_subquery->OutputSymbols(symbol_table), - produce_right_union_subquery->OutputSymbols(symbol_table)); + produce_left_union_subquery->OutputSymbols(this->symbol_table), + produce_right_union_subquery->OutputSymbols(this->symbol_table)); auto apply = std::make_shared<Apply>(n.op_, union_operator, true); auto produce = MakeProduce(apply, return_n, return_m); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 8); } -TEST_F(SubqueriesFeature, SubqueryWithUnion) { +TYPED_TEST(SubqueriesFeature, SubqueryWithUnion) { // MATCH (n) CALL { MATCH (m) RETURN m UNION MATCH (m) RETURN m } RETURN n, m - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); - auto m1 = MakeScanAll(storage, symbol_table, "m"); + auto m1 = MakeScanAll(this->storage, this->symbol_table, "m"); - auto subquery_return_symbol = symbol_table.CreateSymbol("named_expression_2", true); + auto subquery_return_symbol = this->symbol_table.CreateSymbol("named_expression_2", true); auto return_m = NEXPR("m", IDENT("m")->MapTo(m1.sym_))->MapTo(subquery_return_symbol); auto produce_left_union_subquery = MakeProduce(m1.op_, return_m); - auto m2 = MakeScanAll(storage, symbol_table, "m"); + auto m2 = MakeScanAll(this->storage, this->symbol_table, "m"); auto produce_right_union_subquery = MakeProduce(m2.op_, return_m); auto union_operator = std::make_shared<Union>(produce_left_union_subquery, produce_right_union_subquery, std::vector<Symbol>{subquery_return_symbol}, - produce_left_union_subquery->OutputSymbols(symbol_table), - produce_right_union_subquery->OutputSymbols(symbol_table)); + produce_left_union_subquery->OutputSymbols(this->symbol_table), + produce_right_union_subquery->OutputSymbols(this->symbol_table)); - auto union_output_symbols = union_operator->OutputSymbols(symbol_table); + auto union_output_symbols = union_operator->OutputSymbols(this->symbol_table); auto distinct = std::make_shared<Distinct>(union_operator, std::vector<Symbol>{union_output_symbols}); auto apply = std::make_shared<Apply>(n.op_, distinct, true); auto produce = MakeProduce(apply, return_n); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 4); } -TEST_F(SubqueriesFeature, SubqueriesWithForeach) { +TYPED_TEST(SubqueriesFeature, SubqueriesWithForeach) { // MATCH (n) CALL { FOREACH (i in range(1, 5) | CREATE (n)) } RETURN n - auto n = MakeScanAll(storage, symbol_table, "n"); - auto return_n = NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true)); + auto n = MakeScanAll(this->storage, this->symbol_table, "n"); + auto return_n = + NEXPR("n", IDENT("n")->MapTo(n.sym_))->MapTo(this->symbol_table.CreateSymbol("named_expression_1", true)); auto once_create = std::make_shared<Once>(); NodeCreationInfo node_creation_info; - node_creation_info.symbol = symbol_table.CreateSymbol("n", true); + node_creation_info.symbol = this->symbol_table.CreateSymbol("n", true); auto create = std::make_shared<plan::CreateNode>(once_create, node_creation_info); auto once_foreach = std::make_shared<Once>(); - auto iteration_symbol = symbol_table.CreateSymbol("i", true); + auto iteration_symbol = this->symbol_table.CreateSymbol("i", true); auto iterating_list = LIST(LITERAL(1), LITERAL(2), LITERAL(3), LITERAL(4), LITERAL(5)); auto foreach = std::make_shared<plan::Foreach>(once_foreach, create, iterating_list, iteration_symbol); auto empty_result = std::make_shared<EmptyResult>(foreach); @@ -3942,7 +4030,7 @@ TEST_F(SubqueriesFeature, SubqueriesWithForeach) { auto produce = MakeProduce(apply, return_n); - auto context = MakeContext(storage, symbol_table, &dba); + auto context = MakeContext(this->storage, this->symbol_table, &this->dba); auto results = CollectProduce(*produce, &context); EXPECT_EQ(results.size(), 2); } diff --git a/tests/unit/query_plan_read_write_typecheck.cpp b/tests/unit/query_plan_read_write_typecheck.cpp index 1b8ebbadd..dba2d5f57 100644 --- a/tests/unit/query_plan_read_write_typecheck.cpp +++ b/tests/unit/query_plan_read_write_typecheck.cpp @@ -11,6 +11,12 @@ #include <gtest/gtest.h> +#include <memory> + +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" + #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/operator.hpp" #include "query/plan/read_write_type_checker.hpp" @@ -21,15 +27,23 @@ using namespace memgraph::query; using namespace memgraph::query::plan; using RWType = ReadWriteTypeChecker::RWType; +template <typename StorageType> class ReadWriteTypeCheckTest : public ::testing::Test { protected: - ReadWriteTypeCheckTest() : db(), dba(db.Access()) {} - + const std::string testSuite = "query_plan_read_write_typecheck"; AstStorage storage; SymbolTable symbol_table; - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor dba; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage{db->Access()}; + memgraph::query::DbAccessor dba{dba_storage.get()}; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } const Symbol &GetSymbol(std::string name) { return symbol_table.CreateSymbol(name, true); } @@ -40,101 +54,106 @@ class ReadWriteTypeCheckTest : public ::testing::Test { } }; -TEST_F(ReadWriteTypeCheckTest, NONEOps) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ReadWriteTypeCheckTest, StorageTypes); + +TYPED_TEST(ReadWriteTypeCheckTest, NONEOps) { std::shared_ptr<LogicalOperator> once = std::make_shared<Once>(); std::shared_ptr<LogicalOperator> produce = std::make_shared<Produce>(once, std::vector<NamedExpression *>{NEXPR("n", IDENT("n"))}); - CheckPlanType(produce.get(), RWType::NONE); + this->CheckPlanType(produce.get(), RWType::NONE); } -TEST_F(ReadWriteTypeCheckTest, CreateNode) { +TYPED_TEST(ReadWriteTypeCheckTest, CreateNode) { std::shared_ptr<LogicalOperator> once = std::make_shared<Once>(); std::shared_ptr<LogicalOperator> create_node = std::make_shared<CreateNode>(once, NodeCreationInfo()); - CheckPlanType(create_node.get(), RWType::W); + this->CheckPlanType(create_node.get(), RWType::W); } -TEST_F(ReadWriteTypeCheckTest, Filter) { - std::shared_ptr<LogicalOperator> scan_all = std::make_shared<ScanAll>(nullptr, GetSymbol("node1")); +TYPED_TEST(ReadWriteTypeCheckTest, Filter) { + std::shared_ptr<LogicalOperator> scan_all = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node1")); std::shared_ptr<LogicalOperator> filter = std::make_shared<Filter>(scan_all, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP("node1", dba.NameToProperty("prop")), LITERAL(0))); + EQ(PROPERTY_LOOKUP(this->dba, "node1", this->dba.NameToProperty("prop")), LITERAL(0))); - CheckPlanType(filter.get(), RWType::R); + this->CheckPlanType(filter.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, ScanAllBy) { +TYPED_TEST(ReadWriteTypeCheckTest, ScanAllBy) { std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAllByLabelPropertyRange>( - nullptr, GetSymbol("node"), dba.NameToLabel("Label"), dba.NameToProperty("prop"), "prop", + nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", memgraph::utils::MakeBoundInclusive<Expression *>(LITERAL(1)), memgraph::utils::MakeBoundExclusive<Expression *>(LITERAL(20))); - last_op = - std::make_shared<ScanAllByLabelPropertyValue>(last_op, GetSymbol("node"), dba.NameToLabel("Label"), - dba.NameToProperty("prop"), "prop", ADD(LITERAL(21), LITERAL(21))); + last_op = std::make_shared<ScanAllByLabelPropertyValue>( + last_op, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop", + ADD(LITERAL(21), LITERAL(21))); - CheckPlanType(last_op.get(), RWType::R); + this->CheckPlanType(last_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, OrderByAndLimit) { +TYPED_TEST(ReadWriteTypeCheckTest, OrderByAndLimit) { // We build an operator tree that would result from e.g. // MATCH (node:label) // WHERE n.property = 5 // RETURN n // ORDER BY n.property // LIMIT 10 - Symbol node_sym = GetSymbol("node"); - memgraph::storage::LabelId label = dba.NameToLabel("label"); - memgraph::storage::PropertyId prop = dba.NameToProperty("property"); + Symbol node_sym = this->GetSymbol("node"); + memgraph::storage::LabelId label = this->dba.NameToLabel("label"); + memgraph::storage::PropertyId prop = this->dba.NameToProperty("property"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<Once>(); last_op = std::make_shared<ScanAllByLabel>(last_op, node_sym, label); last_op = std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{}, - EQ(PROPERTY_LOOKUP("node", prop), LITERAL(5))); + EQ(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(5))); last_op = std::make_shared<Produce>(last_op, std::vector<NamedExpression *>{NEXPR("n", IDENT("n"))}); - last_op = std::make_shared<OrderBy>(last_op, std::vector<SortItem>{{Ordering::DESC, PROPERTY_LOOKUP("node", prop)}}, + last_op = std::make_shared<OrderBy>(last_op, + std::vector<SortItem>{{Ordering::DESC, PROPERTY_LOOKUP(this->dba, "node", prop)}}, std::vector<Symbol>{node_sym}); last_op = std::make_shared<Limit>(last_op, LITERAL(10)); - CheckPlanType(last_op.get(), RWType::R); + this->CheckPlanType(last_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, Delete) { - auto node_sym = GetSymbol("node1"); +TYPED_TEST(ReadWriteTypeCheckTest, Delete) { + auto node_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym); - last_op = - std::make_shared<Expand>(last_op, node_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Direction::BOTH, - std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); + last_op = std::make_shared<Expand>(last_op, node_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), + EdgeAtom::Direction::BOTH, std::vector<memgraph::storage::EdgeTypeId>{}, false, + memgraph::storage::View::OLD); last_op = std::make_shared<plan::Delete>(last_op, std::vector<Expression *>{IDENT("node2")}, true); - CheckPlanType(last_op.get(), RWType::RW); + this->CheckPlanType(last_op.get(), RWType::RW); } -TEST_F(ReadWriteTypeCheckTest, ExpandVariable) { - auto node1_sym = GetSymbol("node1"); +TYPED_TEST(ReadWriteTypeCheckTest, ExpandVariable) { + auto node1_sym = this->GetSymbol("node1"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<ExpandVariable>( - last_op, node1_sym, GetSymbol("node2"), GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST, + last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT, - std::vector<memgraph::storage::EdgeTypeId>{dba.NameToEdgeType("EdgeType1"), dba.NameToEdgeType("EdgeType2")}, + std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"), + this->dba.NameToEdgeType("EdgeType2")}, false, LITERAL(2), LITERAL(5), false, - ExpansionLambda{GetSymbol("inner_node"), GetSymbol("inner_edge"), - PROPERTY_LOOKUP("inner_node", dba.NameToProperty("unblocked"))}, + ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"), + PROPERTY_LOOKUP(this->dba, "inner_node", this->dba.NameToProperty("unblocked"))}, std::nullopt, std::nullopt); - CheckPlanType(last_op.get(), RWType::R); + this->CheckPlanType(last_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, EdgeUniquenessFilter) { - auto node1_sym = GetSymbol("node1"); - auto node2_sym = GetSymbol("node2"); - auto node3_sym = GetSymbol("node3"); - auto node4_sym = GetSymbol("node4"); +TYPED_TEST(ReadWriteTypeCheckTest, EdgeUniquenessFilter) { + auto node1_sym = this->GetSymbol("node1"); + auto node2_sym = this->GetSymbol("node2"); + auto node3_sym = this->GetSymbol("node3"); + auto node4_sym = this->GetSymbol("node4"); - auto edge1_sym = GetSymbol("edge1"); - auto edge2_sym = GetSymbol("edge2"); + auto edge1_sym = this->GetSymbol("edge1"); + auto edge2_sym = this->GetSymbol("edge2"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::IN, @@ -144,100 +163,102 @@ TEST_F(ReadWriteTypeCheckTest, EdgeUniquenessFilter) { std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); last_op = std::make_shared<EdgeUniquenessFilter>(last_op, edge2_sym, std::vector<Symbol>{edge1_sym}); - CheckPlanType(last_op.get(), RWType::R); + this->CheckPlanType(last_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, SetRemovePropertiesLabels) { - auto node_sym = GetSymbol("node"); - memgraph::storage::PropertyId prop = dba.NameToProperty("prop"); +TYPED_TEST(ReadWriteTypeCheckTest, SetRemovePropertiesLabels) { + auto node_sym = this->GetSymbol("node"); + memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop"); - std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, GetSymbol("node")); - last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP("node", prop), - ADD(PROPERTY_LOOKUP("node", prop), LITERAL(1))); - last_op = std::make_shared<plan::RemoveProperty>(last_op, dba.NameToProperty("prop"), - PROPERTY_LOOKUP("node", dba.NameToProperty("prop"))); - last_op = - std::make_shared<plan::SetProperties>(last_op, node_sym, - MAP({{storage.GetPropertyIx("prop1"), LITERAL(1)}, - {storage.GetPropertyIx("prop2"), LITERAL("this is a property")}}), - plan::SetProperties::Op::REPLACE); + std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node")); + last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop), + ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1))); + last_op = std::make_shared<plan::RemoveProperty>( + last_op, this->dba.NameToProperty("prop"), PROPERTY_LOOKUP(this->dba, "node", this->dba.NameToProperty("prop"))); + last_op = std::make_shared<plan::SetProperties>( + last_op, node_sym, + MAP({{this->storage.GetPropertyIx("prop1"), LITERAL(1)}, + {this->storage.GetPropertyIx("prop2"), LITERAL("this is a property")}}), + plan::SetProperties::Op::REPLACE); last_op = std::make_shared<plan::SetLabels>( - last_op, node_sym, std::vector<memgraph::storage::LabelId>{dba.NameToLabel("label1"), dba.NameToLabel("label2")}); + last_op, node_sym, + std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); last_op = std::make_shared<plan::RemoveLabels>( - last_op, node_sym, std::vector<memgraph::storage::LabelId>{dba.NameToLabel("label1"), dba.NameToLabel("label2")}); + last_op, node_sym, + std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")}); - CheckPlanType(last_op.get(), RWType::RW); + this->CheckPlanType(last_op.get(), RWType::RW); } -TEST_F(ReadWriteTypeCheckTest, Cartesian) { - Symbol x = GetSymbol("x"); +TYPED_TEST(ReadWriteTypeCheckTest, Cartesian) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), x); - Symbol node = GetSymbol("node"); + Symbol node = this->GetSymbol("node"); std::shared_ptr<LogicalOperator> rhs = std::make_shared<ScanAll>(nullptr, node); std::shared_ptr<LogicalOperator> cartesian = std::make_shared<Cartesian>(lhs, std::vector<Symbol>{x}, rhs, std::vector<Symbol>{node}); - CheckPlanType(cartesian.get(), RWType::R); + this->CheckPlanType(cartesian.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, Union) { - Symbol x = GetSymbol("x"); +TYPED_TEST(ReadWriteTypeCheckTest, Union) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> lhs = std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x); - Symbol node = GetSymbol("x"); + Symbol node = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> rhs = std::make_shared<ScanAll>(nullptr, node); std::shared_ptr<LogicalOperator> union_op = std::make_shared<Union>( - lhs, rhs, std::vector<Symbol>{GetSymbol("x")}, std::vector<Symbol>{x}, std::vector<Symbol>{node}); + lhs, rhs, std::vector<Symbol>{this->GetSymbol("x")}, std::vector<Symbol>{x}, std::vector<Symbol>{node}); - CheckPlanType(union_op.get(), RWType::R); + this->CheckPlanType(union_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, CallReadProcedure) { +TYPED_TEST(ReadWriteTypeCheckTest, CallReadProcedure) { plan::CallProcedure call_op; call_op.input_ = std::make_shared<Once>(); call_op.procedure_name_ = "mg.reload"; call_op.arguments_ = {LITERAL("example")}; call_op.result_fields_ = {"name", "signature"}; call_op.is_write_ = false; - call_op.result_symbols_ = {GetSymbol("name_alias"), GetSymbol("signature_alias")}; + call_op.result_symbols_ = {this->GetSymbol("name_alias"), this->GetSymbol("signature_alias")}; - CheckPlanType(&call_op, RWType::R); + this->CheckPlanType(&call_op, RWType::R); } -TEST_F(ReadWriteTypeCheckTest, CallWriteProcedure) { +TYPED_TEST(ReadWriteTypeCheckTest, CallWriteProcedure) { plan::CallProcedure call_op; call_op.input_ = std::make_shared<Once>(); call_op.procedure_name_ = "mg.reload"; call_op.arguments_ = {LITERAL("example")}; call_op.result_fields_ = {"name", "signature"}; call_op.is_write_ = true; - call_op.result_symbols_ = {GetSymbol("name_alias"), GetSymbol("signature_alias")}; + call_op.result_symbols_ = {this->GetSymbol("name_alias"), this->GetSymbol("signature_alias")}; - CheckPlanType(&call_op, RWType::RW); + this->CheckPlanType(&call_op, RWType::RW); } -TEST_F(ReadWriteTypeCheckTest, CallReadProcedureBeforeUpdate) { +TYPED_TEST(ReadWriteTypeCheckTest, CallReadProcedureBeforeUpdate) { std::shared_ptr<LogicalOperator> last_op = std::make_shared<Once>(); last_op = std::make_shared<CreateNode>(last_op, NodeCreationInfo()); std::string procedure_name{"mg.reload"}; std::vector<Expression *> arguments{LITERAL("example")}; std::vector<std::string> result_fields{"name", "signature"}; - std::vector<Symbol> result_symbols{GetSymbol("name_alias"), GetSymbol("signature_alias")}; + std::vector<Symbol> result_symbols{this->GetSymbol("name_alias"), this->GetSymbol("signature_alias")}; last_op = std::make_shared<plan::CallProcedure>(last_op, procedure_name, arguments, result_fields, result_symbols, nullptr, 0, false); - CheckPlanType(last_op.get(), RWType::RW); + this->CheckPlanType(last_op.get(), RWType::RW); } -TEST_F(ReadWriteTypeCheckTest, ConstructNamedPath) { - auto node1_sym = GetSymbol("node1"); - auto edge1_sym = GetSymbol("edge1"); - auto node2_sym = GetSymbol("node2"); - auto edge2_sym = GetSymbol("edge2"); - auto node3_sym = GetSymbol("node3"); +TYPED_TEST(ReadWriteTypeCheckTest, ConstructNamedPath) { + auto node1_sym = this->GetSymbol("node1"); + auto edge1_sym = this->GetSymbol("edge1"); + auto node2_sym = this->GetSymbol("node2"); + auto edge2_sym = this->GetSymbol("edge2"); + auto node3_sym = this->GetSymbol("node3"); std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym); last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::OUT, @@ -245,18 +266,18 @@ TEST_F(ReadWriteTypeCheckTest, ConstructNamedPath) { last_op = std::make_shared<Expand>(last_op, node2_sym, node3_sym, edge2_sym, EdgeAtom::Direction::OUT, std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD); last_op = std::make_shared<ConstructNamedPath>( - last_op, GetSymbol("path"), std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym}); + last_op, this->GetSymbol("path"), std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym}); - CheckPlanType(last_op.get(), RWType::R); + this->CheckPlanType(last_op.get(), RWType::R); } -TEST_F(ReadWriteTypeCheckTest, Foreach) { - Symbol x = GetSymbol("x"); +TYPED_TEST(ReadWriteTypeCheckTest, Foreach) { + Symbol x = this->GetSymbol("x"); std::shared_ptr<LogicalOperator> foreach = std::make_shared<plan::Foreach>(nullptr, nullptr, nullptr, x); - CheckPlanType(foreach.get(), RWType::RW); + this->CheckPlanType(foreach.get(), RWType::RW); } -TEST_F(ReadWriteTypeCheckTest, CheckUpdateType) { +TYPED_TEST(ReadWriteTypeCheckTest, CheckUpdateType) { std::array<std::array<RWType, 3>, 16> scenarios = {{ {RWType::NONE, RWType::NONE, RWType::NONE}, {RWType::NONE, RWType::R, RWType::R}, diff --git a/tests/unit/query_plan_v2_create_set_remove_delete.cpp b/tests/unit/query_plan_v2_create_set_remove_delete.cpp index 106aaf700..93aa6c601 100644 --- a/tests/unit/query_plan_v2_create_set_remove_delete.cpp +++ b/tests/unit/query_plan_v2_create_set_remove_delete.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -9,17 +9,35 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include "disk_test_utils.hpp" #include "query_plan_common.hpp" #include <gtest/gtest.h> #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/operator.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" -TEST(QueryPlan, CreateNodeWithAttributes) { - memgraph::storage::Storage db; - auto dba = db.Access(); +template <typename StorageType> +class QueryPlan : public testing::Test { + public: + const std::string testSuite = "query_plan_v2_create_set_remove_delete"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db = std::make_unique<StorageType>(config); + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(QueryPlan, StorageTypes); + +TYPED_TEST(QueryPlan, CreateNodeWithAttributes) { + auto dba = this->db->Access(); auto label = memgraph::storage::LabelId::FromInt(42); auto property = memgraph::storage::PropertyId::FromInt(1); @@ -34,7 +52,7 @@ TEST(QueryPlan, CreateNodeWithAttributes) { .emplace_back(property, ast.Create<PrimitiveLiteral>(42)); memgraph::query::plan::CreateNode create_node(nullptr, node); - DbAccessor execution_dba(&dba); + DbAccessor execution_dba(dba.get()); auto context = MakeContext(ast, symbol_table, &execution_dba); Frame frame(context.symbol_table.max_position()); auto cursor = create_node.MakeCursor(memgraph::utils::NewDeleteResource()); @@ -46,6 +64,8 @@ TEST(QueryPlan, CreateNodeWithAttributes) { const auto &v = node_value.ValueVertex(); EXPECT_TRUE(*v.HasLabel(memgraph::storage::View::NEW, label)); EXPECT_EQ(v.GetProperty(memgraph::storage::View::NEW, property)->ValueInt(), 42); + dba->PrefetchInEdges(v.impl_); + dba->PrefetchOutEdges(v.impl_); EXPECT_EQ(CountIterable(*v.InEdges(memgraph::storage::View::NEW)), 0); EXPECT_EQ(CountIterable(*v.OutEdges(memgraph::storage::View::NEW)), 0); // Invokes LOG(FATAL) instead of erroring out. @@ -54,12 +74,11 @@ TEST(QueryPlan, CreateNodeWithAttributes) { EXPECT_EQ(count, 1); } -TEST(QueryPlan, ScanAllEmpty) { +TYPED_TEST(QueryPlan, ScanAllEmpty) { memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; - memgraph::storage::Storage db; - auto dba = db.Access(); - DbAccessor execution_dba(&dba); + auto dba = this->db->Access(); + DbAccessor execution_dba(dba.get()); auto node_symbol = symbol_table.CreateSymbol("n", true); { memgraph::query::plan::ScanAll scan_all(nullptr, node_symbol, memgraph::storage::View::OLD); @@ -81,17 +100,16 @@ TEST(QueryPlan, ScanAllEmpty) { } } -TEST(QueryPlan, ScanAll) { - memgraph::storage::Storage db; +TYPED_TEST(QueryPlan, ScanAll) { { - auto dba = db.Access(); - for (int i = 0; i < 42; ++i) dba.CreateVertex(); - EXPECT_FALSE(dba.Commit().HasError()); + auto dba = this->db->Access(); + for (int i = 0; i < 42; ++i) dba->CreateVertex(); + EXPECT_FALSE(dba->Commit().HasError()); } memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; - auto dba = db.Access(); - DbAccessor execution_dba(&dba); + auto dba = this->db->Access(); + DbAccessor execution_dba(dba.get()); auto node_symbol = symbol_table.CreateSymbol("n", true); memgraph::query::plan::ScanAll scan_all(nullptr, node_symbol); auto context = MakeContext(ast, symbol_table, &execution_dba); @@ -102,26 +120,25 @@ TEST(QueryPlan, ScanAll) { EXPECT_EQ(count, 42); } -TEST(QueryPlan, ScanAllByLabel) { - memgraph::storage::Storage db; - auto label = db.NameToLabel("label"); - ASSERT_FALSE(db.CreateIndex(label).HasError()); +TYPED_TEST(QueryPlan, ScanAllByLabel) { + auto label = this->db->NameToLabel("label"); + ASSERT_FALSE(this->db->CreateIndex(label).HasError()); { - auto dba = db.Access(); + auto dba = this->db->Access(); // Add some unlabeled vertices - for (int i = 0; i < 12; ++i) dba.CreateVertex(); + for (int i = 0; i < 12; ++i) dba->CreateVertex(); // Add labeled vertices for (int i = 0; i < 42; ++i) { - auto v = dba.CreateVertex(); + auto v = dba->CreateVertex(); ASSERT_TRUE(v.AddLabel(label).HasValue()); } - EXPECT_FALSE(dba.Commit().HasError()); + EXPECT_FALSE(dba->Commit().HasError()); } - auto dba = db.Access(); + auto dba = this->db->Access(); memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; auto node_symbol = symbol_table.CreateSymbol("n", true); - DbAccessor execution_dba(&dba); + DbAccessor execution_dba(dba.get()); memgraph::query::plan::ScanAllByLabel scan_all(nullptr, node_symbol, label); auto context = MakeContext(ast, symbol_table, &execution_dba); Frame frame(context.symbol_table.max_position()); diff --git a/tests/unit/query_pretty_print.cpp b/tests/unit/query_pretty_print.cpp index 1f5893253..1b19f8aa8 100644 --- a/tests/unit/query_pretty_print.cpp +++ b/tests/unit/query_pretty_print.cpp @@ -15,9 +15,12 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "query/frontend/ast/ast.hpp" #include "query/frontend/ast/pretty_print.hpp" #include "query_common.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "utils/string.hpp" using namespace memgraph::query; @@ -27,14 +30,27 @@ using testing::UnorderedElementsAre; namespace { -struct ExpressionPrettyPrinterTest : public ::testing::Test { - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; +template <typename StorageType> +class ExpressionPrettyPrinterTest : public ::testing::Test { + public: + const std::string testSuite = "query_pretty_print"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } }; -TEST_F(ExpressionPrettyPrinterTest, Literals) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ExpressionPrettyPrinterTest, StorageTypes); + +TYPED_TEST(ExpressionPrettyPrinterTest, Literals) { // 1 EXPECT_EQ(ToString(LITERAL(1)), "1"); @@ -74,16 +90,17 @@ TEST_F(ExpressionPrettyPrinterTest, Literals) { // map {literalEntry: 10, variableSelector: a, .map, .*} auto elements = std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *>{ - {storage.GetPropertyIx("literalEntry"), LITERAL(10)}, - {storage.GetPropertyIx("variableSelector"), IDENT("a")}, - {storage.GetPropertyIx("propertySelector"), PROPERTY_LOOKUP("map", PROPERTY_PAIR("hello"))}, - {storage.GetPropertyIx("allPropertiesSelector"), ALL_PROPERTIES_LOOKUP("map")}}; + {this->storage.GetPropertyIx("literalEntry"), LITERAL(10)}, + {this->storage.GetPropertyIx("variableSelector"), IDENT("a")}, + {this->storage.GetPropertyIx("propertySelector"), + PROPERTY_LOOKUP(this->dba, "map", PROPERTY_PAIR(this->dba, "hello"))}, + {this->storage.GetPropertyIx("allPropertiesSelector"), ALL_PROPERTIES_LOOKUP("map")}}; EXPECT_EQ(ToString(MAP_PROJECTION(IDENT("map"), elements)), "(Identifier \"map\"){\"allPropertiesSelector\": .*, \"literalEntry\": 10, \"propertySelector\": " "(PropertyLookup (Identifier \"map\") \"hello\"), \"variableSelector\": (Identifier \"a\")}"); } -TEST_F(ExpressionPrettyPrinterTest, Identifiers) { +TYPED_TEST(ExpressionPrettyPrinterTest, Identifiers) { // x EXPECT_EQ(ToString(IDENT("x")), "(Identifier \"x\")"); @@ -91,11 +108,11 @@ TEST_F(ExpressionPrettyPrinterTest, Identifiers) { EXPECT_EQ(ToString(IDENT("hello_there")), "(Identifier \"hello_there\")"); } -TEST_F(ExpressionPrettyPrinterTest, Reducing) { +TYPED_TEST(ExpressionPrettyPrinterTest, Reducing) { // all(x in list where x.prop = 42) - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); EXPECT_EQ(ToString(ALL("x", LITERAL(std::vector<memgraph::storage::PropertyValue>{}), - WHERE(EQ(PROPERTY_LOOKUP("x", prop), LITERAL(42))))), + WHERE(EQ(PROPERTY_LOOKUP(this->dba, "x", prop), LITERAL(42))))), "(All (Identifier \"x\") [] (== (PropertyLookup " "(Identifier \"x\") \"prop\") 42))"); @@ -106,7 +123,7 @@ TEST_F(ExpressionPrettyPrinterTest, Reducing) { "\"expression\"))"); } -TEST_F(ExpressionPrettyPrinterTest, UnaryOperators) { +TYPED_TEST(ExpressionPrettyPrinterTest, UnaryOperators) { // not(false) EXPECT_EQ(ToString(NOT(LITERAL(false))), "(Not false)"); @@ -120,23 +137,24 @@ TEST_F(ExpressionPrettyPrinterTest, UnaryOperators) { EXPECT_EQ(ToString(IS_NULL(LITERAL(TypedValue()))), "(IsNull null)"); } -TEST_F(ExpressionPrettyPrinterTest, BinaryOperators) { +TYPED_TEST(ExpressionPrettyPrinterTest, BinaryOperators) { // and(null, 5) EXPECT_EQ(ToString(AND(LITERAL(TypedValue()), LITERAL(5))), "(And null 5)"); // or(5, {hello: "there"}["hello"]) - EXPECT_EQ( - ToString(OR(LITERAL(5), - PROPERTY_LOOKUP(MAP(std::make_pair(storage.GetPropertyIx("hello"), LITERAL("there"))), "hello"))), - "(Or 5 (PropertyLookup {\"hello\": \"there\"} \"hello\"))"); + EXPECT_EQ(ToString(OR( + LITERAL(5), + PROPERTY_LOOKUP(this->dba, MAP(std::make_pair(this->storage.GetPropertyIx("hello"), LITERAL("there"))), + "hello"))), + "(Or 5 (PropertyLookup {\"hello\": \"there\"} \"hello\"))"); // and(coalesce(null, 1), {hello: "there"}) EXPECT_EQ(ToString(AND(COALESCE(LITERAL(TypedValue()), LITERAL(1)), - MAP(std::make_pair(storage.GetPropertyIx("hello"), LITERAL("there"))))), + MAP(std::make_pair(this->storage.GetPropertyIx("hello"), LITERAL("there"))))), "(And (Coalesce [null, 1]) {\"hello\": \"there\"})"); } -TEST_F(ExpressionPrettyPrinterTest, Coalesce) { +TYPED_TEST(ExpressionPrettyPrinterTest, Coalesce) { // coalesce() EXPECT_EQ(ToString(COALESCE()), "(Coalesce [])"); @@ -159,18 +177,19 @@ TEST_F(ExpressionPrettyPrinterTest, Coalesce) { "(Coalesce [[null, null]])"); } -TEST_F(ExpressionPrettyPrinterTest, ParameterLookup) { +TYPED_TEST(ExpressionPrettyPrinterTest, ParameterLookup) { // and($hello, $there) EXPECT_EQ(ToString(AND(PARAMETER_LOOKUP(1), PARAMETER_LOOKUP(2))), "(And (ParameterLookup 1) (ParameterLookup 2))"); } -TEST_F(ExpressionPrettyPrinterTest, PropertyLookup) { +TYPED_TEST(ExpressionPrettyPrinterTest, PropertyLookup) { // {hello: "there"}["hello"] - EXPECT_EQ(ToString(PROPERTY_LOOKUP(MAP(std::make_pair(storage.GetPropertyIx("hello"), LITERAL("there"))), "hello")), + EXPECT_EQ(ToString(PROPERTY_LOOKUP( + this->dba, MAP(std::make_pair(this->storage.GetPropertyIx("hello"), LITERAL("there"))), "hello")), "(PropertyLookup {\"hello\": \"there\"} \"hello\")"); } -TEST_F(ExpressionPrettyPrinterTest, NamedExpression) { +TYPED_TEST(ExpressionPrettyPrinterTest, NamedExpression) { // n AS 1 EXPECT_EQ(ToString(NEXPR("n", LITERAL(1))), "(NamedExpression \"n\" 1)"); } diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp index c9a2800b7..3ebdcbec2 100644 --- a/tests/unit/query_procedure_mgp_type.cpp +++ b/tests/unit/query_procedure_mgp_type.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,10 +17,30 @@ #include "query/procedure/cypher_types.hpp" #include "query/procedure/mg_procedure_impl.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" +#include "disk_test_utils.hpp" #include "test_utils.hpp" -TEST(CypherType, PresentableNameSimpleTypes) { +template <typename StorageType> +class CypherType : public testing::Test { + public: + const std::string testSuite = "query_procedure_mgp_type"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(CypherType, StorageTypes); + +TYPED_TEST(CypherType, PresentableNameSimpleTypes) { EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)->impl->GetPresentableName(), "ANY"); EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_bool)->impl->GetPresentableName(), "BOOLEAN"); EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string)->impl->GetPresentableName(), "STRING"); @@ -33,7 +53,7 @@ TEST(CypherType, PresentableNameSimpleTypes) { EXPECT_EQ(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_path)->impl->GetPresentableName(), "PATH"); } -TEST(CypherType, PresentableNameCompositeTypes) { +TYPED_TEST(CypherType, PresentableNameCompositeTypes) { mgp_type *any_type = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any); { auto *nullable_any = EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, any_type); @@ -82,7 +102,7 @@ TEST(CypherType, PresentableNameCompositeTypes) { } } -TEST(CypherType, NullSatisfiesType) { +TYPED_TEST(CypherType, NullSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; { auto *mgp_null = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory); @@ -134,7 +154,7 @@ static void CheckNotSatisfiesTypesAndListAndNullable(const mgp_value *mgp_val, c } } -TEST(CypherType, BoolSatisfiesType) { +TYPED_TEST(CypherType, BoolSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *mgp_bool = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory); const memgraph::query::TypedValue tv_bool(true); @@ -150,7 +170,7 @@ TEST(CypherType, BoolSatisfiesType) { mgp_value_destroy(mgp_bool); } -TEST(CypherType, IntSatisfiesType) { +TYPED_TEST(CypherType, IntSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *mgp_int = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory); const memgraph::query::TypedValue tv_int(42); @@ -167,7 +187,7 @@ TEST(CypherType, IntSatisfiesType) { mgp_value_destroy(mgp_int); } -TEST(CypherType, DoubleSatisfiesType) { +TYPED_TEST(CypherType, DoubleSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *mgp_double = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, 42, &memory); const memgraph::query::TypedValue tv_double(42.0); @@ -184,7 +204,7 @@ TEST(CypherType, DoubleSatisfiesType) { mgp_value_destroy(mgp_double); } -TEST(CypherType, StringSatisfiesType) { +TYPED_TEST(CypherType, StringSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *mgp_string = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "text", &memory); const memgraph::query::TypedValue tv_string("text"); @@ -200,7 +220,7 @@ TEST(CypherType, StringSatisfiesType) { mgp_value_destroy(mgp_string); } -TEST(CypherType, MapSatisfiesType) { +TYPED_TEST(CypherType, MapSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *map = EXPECT_MGP_NO_ERROR(mgp_map *, mgp_map_make_empty, &memory); EXPECT_EQ( @@ -223,10 +243,9 @@ TEST(CypherType, MapSatisfiesType) { mgp_value_destroy(mgp_map_v); } -TEST(CypherType, VertexSatisfiesType) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(CypherType, VertexSatisfiesType) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); mgp_memory memory{memgraph::utils::NewDeleteResource()}; memgraph::utils::Allocator<mgp_vertex> alloc(memory.impl); @@ -247,10 +266,9 @@ TEST(CypherType, VertexSatisfiesType) { mgp_value_destroy(mgp_vertex_v); } -TEST(CypherType, EdgeSatisfiesType) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(CypherType, EdgeSatisfiesType) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); @@ -272,10 +290,9 @@ TEST(CypherType, EdgeSatisfiesType) { mgp_value_destroy(mgp_edge_v); } -TEST(CypherType, PathSatisfiesType) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(CypherType, PathSatisfiesType) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); auto edge = *dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge_type")); @@ -314,7 +331,7 @@ static std::vector<mgp_type *> MakeListTypes(const std::vector<mgp_type *> &elem return list_types; } -TEST(CypherType, EmptyListSatisfiesType) { +TYPED_TEST(CypherType, EmptyListSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 0, &memory); auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); @@ -332,7 +349,7 @@ TEST(CypherType, EmptyListSatisfiesType) { mgp_value_destroy(mgp_list_v); } -TEST(CypherType, ListOfIntSatisfiesType) { +TYPED_TEST(CypherType, ListOfIntSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; static constexpr int64_t elem_count = 3; auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, elem_count, &memory); @@ -360,7 +377,7 @@ TEST(CypherType, ListOfIntSatisfiesType) { mgp_value_destroy(mgp_list_v); } -TEST(CypherType, ListOfIntAndBoolSatisfiesType) { +TYPED_TEST(CypherType, ListOfIntAndBoolSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; static constexpr int64_t elem_count = 2; auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, elem_count, &memory); @@ -394,7 +411,7 @@ TEST(CypherType, ListOfIntAndBoolSatisfiesType) { mgp_value_destroy(mgp_list_v); } -TEST(CypherType, ListOfNullSatisfiesType) { +TYPED_TEST(CypherType, ListOfNullSatisfiesType) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 1, &memory); auto *mgp_list_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_list, list); diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp index 883489643..abe8b7f27 100644 --- a/tests/unit/query_procedure_py_module.cpp +++ b/tests/unit/query_procedure_py_module.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,11 +14,31 @@ #include <filesystem> #include <string> +#include "disk_test_utils.hpp" #include "query/procedure/mg_procedure_impl.hpp" #include "query/procedure/py_module.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "test_utils.hpp" -TEST(PyModule, MgpValueToPyObject) { +template <typename StorageType> +class PyModule : public testing::Test { + public: + const std::string testSuite = "query_procedure_py_module"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(PyModule, StorageTypes); + +TYPED_TEST(PyModule, MgpValueToPyObject) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto *list = EXPECT_MGP_NO_ERROR(mgp_list *, mgp_list_make_empty, 42, &memory); { @@ -93,25 +113,24 @@ static void AssertPickleAndCopyAreNotSupported(PyObject *py_obj) { ASSERT_TRUE(memgraph::py::FetchError()); } -TEST(PyModule, PyVertex) { +TYPED_TEST(PyModule, PyVertex) { // Initialize the database with 2 vertices and 1 edge. - memgraph::storage::Storage db; { - auto dba = db.Access(); - auto v1 = dba.CreateVertex(); - auto v2 = dba.CreateVertex(); + auto dba = this->db->Access(); + auto v1 = dba->CreateVertex(); + auto v2 = dba->CreateVertex(); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("key1"), memgraph::storage::PropertyValue("value1")).HasValue()); - ASSERT_TRUE(v1.SetProperty(dba.NameToProperty("key2"), memgraph::storage::PropertyValue(1337)).HasValue()); + ASSERT_TRUE(v1.SetProperty(dba->NameToProperty("key1"), memgraph::storage::PropertyValue("value1")).HasValue()); + ASSERT_TRUE(v1.SetProperty(dba->NameToProperty("key2"), memgraph::storage::PropertyValue(1337)).HasValue()); - auto e = dba.CreateEdge(&v1, &v2, dba.NameToEdgeType("type")); + auto e = dba->CreateEdge(&v1, &v2, dba->NameToEdgeType("type")); ASSERT_TRUE(e.HasValue()); - ASSERT_FALSE(dba.Commit().HasError()); + ASSERT_FALSE(dba->Commit().HasError()); } // Get the first vertex as an mgp_value. - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD}; auto *vertex = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); @@ -143,24 +162,25 @@ TEST(PyModule, PyVertex) { ASSERT_FALSE(dba.Commit().HasError()); } -TEST(PyModule, PyEdge) { +TYPED_TEST(PyModule, PyEdge) { // Initialize the database with 2 vertices and 1 edge. - memgraph::storage::Storage db; { - auto dba = db.Access(); - auto v1 = dba.CreateVertex(); - auto v2 = dba.CreateVertex(); + auto dba = this->db->Access(); + auto v1 = dba->CreateVertex(); + auto v2 = dba->CreateVertex(); - auto e = dba.CreateEdge(&v1, &v2, dba.NameToEdgeType("type")); + auto e = dba->CreateEdge(&v1, &v2, dba->NameToEdgeType("type")); ASSERT_TRUE(e.HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("key1"), memgraph::storage::PropertyValue("value1")).HasValue()); - ASSERT_TRUE(e->SetProperty(dba.NameToProperty("key2"), memgraph::storage::PropertyValue(1337)).HasValue()); - ASSERT_FALSE(dba.Commit().HasError()); + ASSERT_TRUE( + e.GetValue().SetProperty(dba->NameToProperty("key1"), memgraph::storage::PropertyValue("value1")).HasValue()); + ASSERT_TRUE( + e.GetValue().SetProperty(dba->NameToProperty("key2"), memgraph::storage::PropertyValue(1337)).HasValue()); + ASSERT_FALSE(dba->Commit().HasError()); } // Get the edge as an mgp_value. - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD}; auto *start_v = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); @@ -197,17 +217,16 @@ TEST(PyModule, PyEdge) { ASSERT_FALSE(dba.Commit().HasError()); } -TEST(PyModule, PyPath) { - memgraph::storage::Storage db; +TYPED_TEST(PyModule, PyPath) { { - auto dba = db.Access(); - auto v1 = dba.CreateVertex(); - auto v2 = dba.CreateVertex(); - ASSERT_TRUE(dba.CreateEdge(&v1, &v2, dba.NameToEdgeType("type")).HasValue()); - ASSERT_FALSE(dba.Commit().HasError()); + auto dba = this->db->Access(); + auto v1 = dba->CreateVertex(); + auto v2 = dba->CreateVertex(); + ASSERT_TRUE(dba->CreateEdge(&v1, &v2, dba->NameToEdgeType("type")).HasValue()); + ASSERT_FALSE(dba->Commit().HasError()); } - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD}; auto *start_v = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{0}, &memory); @@ -245,7 +264,7 @@ TEST(PyModule, PyPath) { ASSERT_FALSE(dba.Commit().HasError()); } -TEST(PyModule, PyObjectToMgpValue) { +TYPED_TEST(PyModule, PyObjectToMgpValue) { mgp_memory memory{memgraph::utils::NewDeleteResource()}; auto gil = memgraph::py::EnsureGIL(); memgraph::py::Object py_value{ diff --git a/tests/unit/query_procedures_mgp_graph.cpp b/tests/unit/query_procedures_mgp_graph.cpp index b2742e8f1..1b8918f9d 100644 --- a/tests/unit/query_procedures_mgp_graph.cpp +++ b/tests/unit/query_procedures_mgp_graph.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,13 +18,15 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "mg_procedure.h" #include "query/db_accessor.hpp" #include "query/plan/operator.hpp" #include "query/procedure/mg_procedure_impl.hpp" +#include "storage/v2/disk/storage.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/property_value.hpp" -#include "storage/v2/storage.hpp" #include "storage/v2/vertex_accessor.hpp" #include "storage/v2/view.hpp" #include "storage_test_utils.hpp" @@ -94,6 +96,18 @@ size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable) { ; void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) { + if (auto dbAccessor = std::get_if<memgraph::query::DbAccessor *>(&from->graph->impl)) { + (*dbAccessor)->PrefetchOutEdges(std::get<memgraph::query::VertexAccessor>(from->impl)); + (*dbAccessor)->PrefetchInEdges(std::get<memgraph::query::VertexAccessor>(from->impl)); + (*dbAccessor)->PrefetchOutEdges(std::get<memgraph::query::VertexAccessor>(to->impl)); + (*dbAccessor)->PrefetchInEdges(std::get<memgraph::query::VertexAccessor>(to->impl)); + } else if (auto dbAccessor = std::get<memgraph::query::SubgraphDbAccessor *>(from->graph->impl)) { + dbAccessor->PrefetchOutEdges(std::get<memgraph::query::SubgraphVertexAccessor>(from->impl)); + dbAccessor->PrefetchInEdges(std::get<memgraph::query::SubgraphVertexAccessor>(from->impl)); + dbAccessor->PrefetchOutEdges(std::get<memgraph::query::SubgraphVertexAccessor>(to->impl)); + dbAccessor->PrefetchInEdges(std::get<memgraph::query::SubgraphVertexAccessor>(to->impl)); + } + EXPECT_EQ( CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, from->impl)), 0); @@ -109,7 +123,9 @@ void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, con } } // namespace -struct MgpGraphTest : public ::testing::Test { +template <typename StorageType> +class MgpGraphTest : public ::testing::Test { + public: mgp_graph CreateGraph(const memgraph::storage::View view = memgraph::storage::View::NEW) { // the execution context can be null as it shouldn't be used in these tests return mgp_graph{&CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION), view, ctx_.get()}; @@ -132,118 +148,147 @@ struct MgpGraphTest : public ::testing::Test { void GetFirstOutEdge(mgp_graph &graph, memgraph::storage::Gid vertex_id, MgpEdgePtr &edge) { MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, - mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; ASSERT_NE(from, nullptr); - MgpEdgesIteratorPtr it{EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)}; + MgpEdgesIteratorPtr it{ + EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &this->memory)}; ASSERT_NE(it, nullptr); auto *edge_from_it = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, it.get()); ASSERT_NE(edge_from_it, nullptr); // Copy is used to get a non const pointer because mgp_edges_iterator_get_mutable doesn't work with immutable graph - edge.reset(EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &memory)); + edge.reset(EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &this->memory)); ASSERT_NE(edge, nullptr); } memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) { - accessors_.push_back(storage.Access(isolationLevel)); - db_accessors_.emplace_back(&accessors_.back()); + accessors_.push_back(storage->Access(isolationLevel)); + db_accessors_.emplace_back(accessors_.back().get()); return db_accessors_.back(); } - memgraph::storage::Storage storage; + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + const std::string testSuite = "query_procedures_mgp_graph"; + + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> storage{new StorageType(config)}; mgp_memory memory{memgraph::utils::NewDeleteResource()}; private: - std::list<memgraph::storage::Storage::Accessor> accessors_; + std::list<std::unique_ptr<memgraph::storage::Storage::Accessor>> accessors_; std::list<memgraph::query::DbAccessor> db_accessors_; std::unique_ptr<memgraph::query::ExecutionContext> ctx_ = std::make_unique<memgraph::query::ExecutionContext>(); }; -TEST_F(MgpGraphTest, IsMutable) { - mgp_graph immutable_graph = CreateGraph(memgraph::storage::View::OLD); +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(MgpGraphTest, StorageTypes); + +TYPED_TEST(MgpGraphTest, IsMutable) { + mgp_graph immutable_graph = this->CreateGraph(memgraph::storage::View::OLD); EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_graph_is_mutable, &immutable_graph), 0); - mgp_graph mutable_graph = CreateGraph(memgraph::storage::View::NEW); + mgp_graph mutable_graph = this->CreateGraph(memgraph::storage::View::NEW); EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_graph_is_mutable, &mutable_graph), 0); } -TEST_F(MgpGraphTest, CreateVertex) { - mgp_graph graph = CreateGraph(); - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 0); - MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &memory)}; +TYPED_TEST(MgpGraphTest, CreateVertex) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } + mgp_graph graph = this->CreateGraph(); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 0); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &this->memory)}; EXPECT_NE(vertex, nullptr); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); const auto vertex_id = EXPECT_MGP_NO_ERROR(mgp_vertex_id, mgp_vertex_get_id, vertex.get()); - EXPECT_TRUE(read_uncommited_accessor - .FindVertex(memgraph::storage::Gid::FromInt(vertex_id.as_int), memgraph::storage::View::NEW) - .has_value()); + EXPECT_TRUE(read_uncommited_accessor->FindVertex(memgraph::storage::Gid::FromInt(vertex_id.as_int), + memgraph::storage::View::NEW)); } -TEST_F(MgpGraphTest, DeleteVertex) { +TYPED_TEST(MgpGraphTest, DeleteVertex) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); const auto vertex = accessor.InsertVertex(); vertex_id = vertex.Gid(); ASSERT_FALSE(accessor.Commit().HasError()); } - mgp_graph graph = CreateGraph(); - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); - MgpVertexPtr vertex{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_graph graph = this->CreateGraph(); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; EXPECT_NE(vertex, nullptr); EXPECT_SUCCESS(mgp_graph_delete_vertex(&graph, vertex.get())); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 0); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 0); } -TEST_F(MgpGraphTest, DetachDeleteVertex) { - const auto vertex_ids = CreateEdge(); - auto graph = CreateGraph(); - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 2); +TYPED_TEST(MgpGraphTest, DetachDeleteVertex) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } + const auto vertex_ids = this->CreateEdge(); + auto graph = this->CreateGraph(); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 2); MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, - mgp_vertex_id{vertex_ids.front().AsInt()}, &memory)}; + mgp_vertex_id{vertex_ids.front().AsInt()}, &this->memory)}; EXPECT_EQ(mgp_graph_delete_vertex(&graph, vertex.get()), mgp_error::MGP_ERROR_LOGIC_ERROR); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 2); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 2); EXPECT_SUCCESS(mgp_graph_detach_delete_vertex(&graph, vertex.get())); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); } -TEST_F(MgpGraphTest, CreateDeleteWithImmutableGraph) { +TYPED_TEST(MgpGraphTest, CreateDeleteWithImmutableGraph) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); const auto vertex = accessor.InsertVertex(); vertex_id = vertex.Gid(); ASSERT_FALSE(accessor.Commit().HasError()); } - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); - mgp_graph immutable_graph = CreateGraph(memgraph::storage::View::OLD); + mgp_graph immutable_graph = this->CreateGraph(memgraph::storage::View::OLD); mgp_vertex *raw_vertex{nullptr}; - EXPECT_EQ(mgp_graph_create_vertex(&immutable_graph, &memory, &raw_vertex), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); + EXPECT_EQ(mgp_graph_create_vertex(&immutable_graph, &this->memory, &raw_vertex), + mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); MgpVertexPtr created_vertex{raw_vertex}; EXPECT_EQ(created_vertex, nullptr); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); MgpVertexPtr vertex_to_delete{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &immutable_graph, - mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; ASSERT_NE(vertex_to_delete, nullptr); EXPECT_EQ(mgp_graph_delete_vertex(&immutable_graph, vertex_to_delete.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); } -TEST_F(MgpGraphTest, VerticesIterator) { +TYPED_TEST(MgpGraphTest, VerticesIterator) { { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); accessor.InsertVertex(); ASSERT_FALSE(accessor.Commit().HasError()); } auto check_vertices_iterator = [this](const memgraph::storage::View view) { - mgp_graph graph = CreateGraph(view); + mgp_graph graph = this->CreateGraph(view); MgpVerticesIteratorPtr vertices_iter{ - EXPECT_MGP_NO_ERROR(mgp_vertices_iterator *, mgp_graph_iter_vertices, &graph, &memory)}; + EXPECT_MGP_NO_ERROR(mgp_vertices_iterator *, mgp_graph_iter_vertices, &graph, &this->memory)}; ASSERT_NE(vertices_iter, nullptr); EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_vertices_iterator_get, vertices_iter.get()); if (view == memgraph::storage::View::NEW) { @@ -262,21 +307,25 @@ TEST_F(MgpGraphTest, VerticesIterator) { } } -TEST_F(MgpGraphTest, VertexIsMutable) { - auto graph = CreateGraph(memgraph::storage::View::NEW); - MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &memory)}; +TYPED_TEST(MgpGraphTest, VertexIsMutable) { + auto graph = this->CreateGraph(memgraph::storage::View::NEW); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &this->memory)}; ASSERT_NE(vertex.get(), nullptr); EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0); graph.view = memgraph::storage::View::OLD; EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0); } -TEST_F(MgpGraphTest, VertexSetProperty) { +TYPED_TEST(MgpGraphTest, VertexSetProperty) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } static constexpr std::string_view property_to_update{"to_update"}; static constexpr std::string_view property_to_set{"to_set"}; memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); auto vertex = accessor.InsertVertex(); vertex_id = vertex.Gid(); const auto result = @@ -284,23 +333,23 @@ TEST_F(MgpGraphTest, VertexSetProperty) { ASSERT_TRUE(result.HasValue()); ASSERT_FALSE(accessor.Commit().HasError()); } - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - EXPECT_EQ(CountVertices(read_uncommited_accessor, memgraph::storage::View::NEW), 1); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); - mgp_graph graph = CreateGraph(memgraph::storage::View::NEW); - MgpVertexPtr vertex{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; ASSERT_NE(vertex, nullptr); - auto vertex_acc = read_uncommited_accessor.FindVertex(vertex_id, memgraph::storage::View::NEW); - ASSERT_TRUE(vertex_acc.has_value()); - const auto property_id_to_update = read_uncommited_accessor.NameToProperty(property_to_update); + auto vertex_acc = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_acc); + const auto property_id_to_update = read_uncommited_accessor->NameToProperty(property_to_update); { SCOPED_TRACE("Update the property"); static constexpr int64_t numerical_value_to_update_to{69}; MgpValuePtr value_to_update_to{ - EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &memory)}; + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &this->memory)}; ASSERT_NE(value_to_update_to, nullptr); EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_update.data(), value_to_update_to.get())); @@ -310,7 +359,7 @@ TEST_F(MgpGraphTest, VertexSetProperty) { } { SCOPED_TRACE("Remove the property"); - MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)}; + MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &this->memory)}; ASSERT_NE(null_value, nullptr); EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_update.data(), null_value.get())); @@ -321,51 +370,60 @@ TEST_F(MgpGraphTest, VertexSetProperty) { { SCOPED_TRACE("Add a property"); static constexpr double numerical_value_to_set{3.5}; - MgpValuePtr value_to_set{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &memory)}; + MgpValuePtr value_to_set{ + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &this->memory)}; ASSERT_NE(value_to_set, nullptr); EXPECT_SUCCESS(mgp_vertex_set_property(vertex.get(), property_to_set.data(), value_to_set.get())); - const auto maybe_prop = - vertex_acc->GetProperty(read_uncommited_accessor.NameToProperty(property_to_set), memgraph::storage::View::NEW); + const auto maybe_prop = vertex_acc->GetProperty(read_uncommited_accessor->NameToProperty(property_to_set), + memgraph::storage::View::NEW); ASSERT_TRUE(maybe_prop.HasValue()); EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_set}); } } -TEST_F(MgpGraphTest, VertexAddLabel) { +TYPED_TEST(MgpGraphTest, VertexAddLabel) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } static constexpr std::string_view label = "test_label"; memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); const auto vertex = accessor.InsertVertex(); vertex_id = vertex.Gid(); ASSERT_FALSE(accessor.Commit().HasError()); } - mgp_graph graph = CreateGraph(memgraph::storage::View::NEW); - MgpVertexPtr vertex{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; EXPECT_SUCCESS(mgp_vertex_add_label(vertex.get(), mgp_label{label.data()})); auto check_label = [&]() { EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0); - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - const auto maybe_vertex = read_uncommited_accessor.FindVertex(vertex_id, memgraph::storage::View::NEW); - ASSERT_TRUE(maybe_vertex.has_value()); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW); + ASSERT_TRUE(maybe_vertex); const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW); ASSERT_TRUE(label_ids.HasValue()); - EXPECT_THAT(*label_ids, ::testing::ContainerEq(std::vector{read_uncommited_accessor.NameToLabel(label)})); + EXPECT_THAT(*label_ids, ::testing::ContainerEq(std::vector{read_uncommited_accessor->NameToLabel(label)})); }; ASSERT_NO_FATAL_FAILURE(check_label()); EXPECT_SUCCESS(mgp_vertex_add_label(vertex.get(), mgp_label{label.data()})); ASSERT_NO_FATAL_FAILURE(check_label()); } -TEST_F(MgpGraphTest, VertexRemoveLabel) { +TYPED_TEST(MgpGraphTest, VertexRemoveLabel) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } static constexpr std::string_view label = "test_label"; memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); auto vertex = accessor.InsertVertex(); const auto result = vertex.AddLabel(accessor.NameToLabel(label)); ASSERT_TRUE(result.HasValue()); @@ -374,17 +432,17 @@ TEST_F(MgpGraphTest, VertexRemoveLabel) { ASSERT_FALSE(accessor.Commit().HasError()); } - mgp_graph graph = CreateGraph(memgraph::storage::View::NEW); - MgpVertexPtr vertex{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; EXPECT_SUCCESS(mgp_vertex_remove_label(vertex.get(), mgp_label{label.data()})); auto check_label = [&]() { EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0); - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - const auto maybe_vertex = read_uncommited_accessor.FindVertex(vertex_id, memgraph::storage::View::NEW); - ASSERT_TRUE(maybe_vertex.has_value()); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW); + ASSERT_TRUE(maybe_vertex); const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW); ASSERT_TRUE(label_ids.HasValue()); EXPECT_EQ(label_ids->size(), 0); @@ -394,58 +452,58 @@ TEST_F(MgpGraphTest, VertexRemoveLabel) { ASSERT_NO_FATAL_FAILURE(check_label()); } -TEST_F(MgpGraphTest, ModifyImmutableVertex) { +TYPED_TEST(MgpGraphTest, ModifyImmutableVertex) { static constexpr std::string_view label_to_remove{"label_to_remove"}; memgraph::storage::Gid vertex_id{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); auto vertex = accessor.InsertVertex(); vertex_id = vertex.Gid(); ASSERT_TRUE(vertex.AddLabel(accessor.NameToLabel(label_to_remove)).HasValue()); ASSERT_FALSE(accessor.Commit().HasError()); } - auto graph = CreateGraph(memgraph::storage::View::OLD); - MgpVertexPtr vertex{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &memory)}; + auto graph = this->CreateGraph(memgraph::storage::View::OLD); + MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, vertex.get()), 0); EXPECT_EQ(mgp_vertex_add_label(vertex.get(), mgp_label{"label"}), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); EXPECT_EQ(mgp_vertex_remove_label(vertex.get(), mgp_label{label_to_remove.data()}), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); - MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 4, &memory)}; + MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 4, &this->memory)}; EXPECT_EQ(mgp_vertex_set_property(vertex.get(), "property", value.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); } -TEST_F(MgpGraphTest, CreateDeleteEdge) { +TYPED_TEST(MgpGraphTest, CreateDeleteEdge) { std::array<memgraph::storage::Gid, 2> vertex_ids{}; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); for (auto i = 0; i < 2; ++i) { vertex_ids[i] = accessor.InsertVertex().Gid(); } ASSERT_FALSE(accessor.Commit().HasError()); } - auto graph = CreateGraph(); + auto graph = this->CreateGraph(); MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, - mgp_vertex_id{vertex_ids[0].AsInt()}, &memory)}; + mgp_vertex_id{vertex_ids[0].AsInt()}, &this->memory)}; MgpVertexPtr to{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, - mgp_vertex_id{vertex_ids[1].AsInt()}, &memory)}; + mgp_vertex_id{vertex_ids[1].AsInt()}, &this->memory)}; ASSERT_NE(from, nullptr); ASSERT_NE(to, nullptr); CheckEdgeCountBetween(from, to, 0); MgpEdgePtr edge{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_graph_create_edge, &graph, from.get(), to.get(), - mgp_edge_type{"EDGE"}, &memory)}; + mgp_edge_type{"EDGE"}, &this->memory)}; CheckEdgeCountBetween(from, to, 1); ASSERT_NE(edge, nullptr); EXPECT_SUCCESS(mgp_graph_delete_edge(&graph, edge.get())); CheckEdgeCountBetween(from, to, 0); } -TEST_F(MgpGraphTest, CreateDeleteEdgeWithImmutableGraph) { +TYPED_TEST(MgpGraphTest, CreateDeleteEdgeWithImmutableGraph) { memgraph::storage::Gid from_id; memgraph::storage::Gid to_id; { - auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); auto from = accessor.InsertVertex(); auto to = accessor.InsertVertex(); from_id = from.Gid(); @@ -453,51 +511,51 @@ TEST_F(MgpGraphTest, CreateDeleteEdgeWithImmutableGraph) { ASSERT_TRUE(accessor.InsertEdge(&from, &to, accessor.NameToEdgeType("EDGE_TYPE_TO_REMOVE")).HasValue()); ASSERT_FALSE(accessor.Commit().HasError()); } - auto graph = CreateGraph(memgraph::storage::View::OLD); - MgpVertexPtr from{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{from_id.AsInt()}, &memory)}; - MgpVertexPtr to{ - EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{to_id.AsInt()}, &memory)}; + auto graph = this->CreateGraph(memgraph::storage::View::OLD); + MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, + mgp_vertex_id{from_id.AsInt()}, &this->memory)}; + MgpVertexPtr to{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{to_id.AsInt()}, + &this->memory)}; ASSERT_NE(from, nullptr); ASSERT_NE(to, nullptr); CheckEdgeCountBetween(from, to, 1); mgp_edge *edge{nullptr}; - EXPECT_EQ( - mgp_graph_create_edge(&graph, from.get(), to.get(), mgp_edge_type{"NEWLY_CREATED_EDGE_TYPE"}, &memory, &edge), - mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); + EXPECT_EQ(mgp_graph_create_edge(&graph, from.get(), to.get(), mgp_edge_type{"NEWLY_CREATED_EDGE_TYPE"}, &this->memory, + &edge), + mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); CheckEdgeCountBetween(from, to, 1); MgpEdgesIteratorPtr edges_it{ - EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)}; + EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &this->memory)}; auto *edge_from_it = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, edges_it.get()); ASSERT_NE(edge_from_it, nullptr); EXPECT_EQ(mgp_graph_delete_edge(&graph, edge_from_it), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); - MgpEdgePtr edge_copy_of_immutable{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &memory)}; + MgpEdgePtr edge_copy_of_immutable{EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edge_copy, edge_from_it, &this->memory)}; EXPECT_EQ(mgp_graph_delete_edge(&graph, edge_copy_of_immutable.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); CheckEdgeCountBetween(from, to, 1); } -TEST_F(MgpGraphTest, EdgeIsMutable) { - const auto vertex_ids = CreateEdge(); - auto graph = CreateGraph(); +TYPED_TEST(MgpGraphTest, EdgeIsMutable) { + const auto vertex_ids = this->CreateEdge(); + auto graph = this->CreateGraph(); MgpEdgePtr edge{}; - ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, vertex_ids[0], edge)); + ASSERT_NO_FATAL_FAILURE(this->GetFirstOutEdge(graph, vertex_ids[0], edge)); EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0); graph.view = memgraph::storage::View::OLD; EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0); } -TEST_F(MgpGraphTest, MutableFromTo) { +TYPED_TEST(MgpGraphTest, MutableFromTo) { memgraph::storage::Gid from_vertex_id{}; { - const auto vertex_ids = CreateEdge(); + const auto vertex_ids = this->CreateEdge(); from_vertex_id = vertex_ids[0]; } auto check_edges_iterator = [this, from_vertex_id](const memgraph::storage::View view) { - mgp_graph graph = CreateGraph(view); + mgp_graph graph = this->CreateGraph(view); MgpEdgePtr edge{}; - ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge)); + ASSERT_NO_FATAL_FAILURE(this->GetFirstOutEdge(graph, from_vertex_id, edge)); auto *from = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_edge_get_from, edge.get()); auto *to = EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_edge_get_from, edge.get()); auto check_is_mutable = [&edge, from, to](bool is_mutable) { @@ -521,18 +579,19 @@ TEST_F(MgpGraphTest, MutableFromTo) { } } -TEST_F(MgpGraphTest, EdgesIterator) { +TYPED_TEST(MgpGraphTest, EdgesIterator) { memgraph::storage::Gid from_vertex_id{}; { - const auto vertex_ids = CreateEdge(); + const auto vertex_ids = this->CreateEdge(); from_vertex_id = vertex_ids[0]; } auto check_edges_iterator = [this, from_vertex_id](const memgraph::storage::View view) { - mgp_graph graph = CreateGraph(view); + mgp_graph graph = this->CreateGraph(view); MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, - mgp_vertex_id{from_vertex_id.AsInt()}, &memory)}; - MgpEdgesIteratorPtr iter{EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &memory)}; + mgp_vertex_id{from_vertex_id.AsInt()}, &this->memory)}; + MgpEdgesIteratorPtr iter{ + EXPECT_MGP_NO_ERROR(mgp_edges_iterator *, mgp_vertex_iter_out_edges, from.get(), &this->memory)}; auto *edge = EXPECT_MGP_NO_ERROR(mgp_edge *, mgp_edges_iterator_get, iter.get()); auto check_is_mutable = [&edge, &iter](bool is_mutable) { EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edges_iterator_underlying_graph_is_mutable, iter.get()) != 0, is_mutable); @@ -554,39 +613,43 @@ TEST_F(MgpGraphTest, EdgesIterator) { } } -TEST_F(MgpGraphTest, EdgeSetProperty) { +TYPED_TEST(MgpGraphTest, EdgeSetProperty) { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { + // DiskStorage doesn't support READ_UNCOMMITTED isolation level + return; + } static constexpr std::string_view property_to_update{"to_update"}; static constexpr std::string_view property_to_set{"to_set"}; memgraph::storage::Gid from_vertex_id{}; - auto get_edge = [&from_vertex_id](memgraph::storage::Storage::Accessor &accessor) -> memgraph::storage::EdgeAccessor { - auto from = accessor.FindVertex(from_vertex_id, memgraph::storage::View::NEW); - return from->OutEdges(memgraph::storage::View::NEW).GetValue().front(); + auto get_edge = [&from_vertex_id](memgraph::storage::Storage::Accessor *accessor) -> memgraph::storage::EdgeAccessor { + auto from = accessor->FindVertex(from_vertex_id, memgraph::storage::View::NEW); + return std::move(from->OutEdges(memgraph::storage::View::NEW).GetValue().front()); }; { - const auto vertex_ids = CreateEdge(); + const auto vertex_ids = this->CreateEdge(); from_vertex_id = vertex_ids[0]; - auto accessor = storage.Access(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); - auto edge = get_edge(accessor); + auto accessor = this->storage->Access(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto edge = get_edge(accessor.get()); const auto result = - edge.SetProperty(accessor.NameToProperty(property_to_update), memgraph::storage::PropertyValue(42)); + edge.SetProperty(accessor->NameToProperty(property_to_update), memgraph::storage::PropertyValue(42)); ASSERT_TRUE(result.HasValue()); - ASSERT_FALSE(accessor.Commit().HasError()); + ASSERT_FALSE(accessor->Commit().HasError()); } - auto read_uncommited_accessor = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); - mgp_graph graph = CreateGraph(memgraph::storage::View::NEW); + mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); MgpEdgePtr edge; - ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge)); - const auto edge_acc = get_edge(read_uncommited_accessor); + ASSERT_NO_FATAL_FAILURE(this->GetFirstOutEdge(graph, from_vertex_id, edge)); + const auto edge_acc = get_edge(read_uncommited_accessor.get()); - const auto property_id_to_update = read_uncommited_accessor.NameToProperty(property_to_update); + const auto property_id_to_update = read_uncommited_accessor->NameToProperty(property_to_update); { SCOPED_TRACE("Update the property"); static constexpr int64_t numerical_value_to_update_to{69}; MgpValuePtr value_to_update_to{ - EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &memory)}; + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, numerical_value_to_update_to, &this->memory)}; ASSERT_NE(value_to_update_to, nullptr); EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_update.data(), value_to_update_to.get())); @@ -596,7 +659,7 @@ TEST_F(MgpGraphTest, EdgeSetProperty) { } { SCOPED_TRACE("Remove the property"); - MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)}; + MgpValuePtr null_value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &this->memory)}; ASSERT_NE(null_value, nullptr); EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_update.data(), null_value.get())); @@ -607,26 +670,27 @@ TEST_F(MgpGraphTest, EdgeSetProperty) { { SCOPED_TRACE("Add a property"); static constexpr double numerical_value_to_set{3.5}; - MgpValuePtr value_to_set{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &memory)}; + MgpValuePtr value_to_set{ + EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_double, numerical_value_to_set, &this->memory)}; ASSERT_NE(value_to_set, nullptr); EXPECT_SUCCESS(mgp_edge_set_property(edge.get(), property_to_set.data(), value_to_set.get())); const auto maybe_prop = - edge_acc.GetProperty(read_uncommited_accessor.NameToProperty(property_to_set), memgraph::storage::View::NEW); + edge_acc.GetProperty(read_uncommited_accessor->NameToProperty(property_to_set), memgraph::storage::View::NEW); ASSERT_TRUE(maybe_prop.HasValue()); EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_set}); } } -TEST_F(MgpGraphTest, EdgeSetPropertyWithImmutableGraph) { +TYPED_TEST(MgpGraphTest, EdgeSetPropertyWithImmutableGraph) { memgraph::storage::Gid from_vertex_id{}; { - const auto vertex_ids = CreateEdge(); + const auto vertex_ids = this->CreateEdge(); from_vertex_id = vertex_ids[0]; } - auto graph = CreateGraph(memgraph::storage::View::OLD); + auto graph = this->CreateGraph(memgraph::storage::View::OLD); MgpEdgePtr edge; - ASSERT_NO_FATAL_FAILURE(GetFirstOutEdge(graph, from_vertex_id, edge)); - MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 65, &memory)}; + ASSERT_NO_FATAL_FAILURE(this->GetFirstOutEdge(graph, from_vertex_id, edge)); + MgpValuePtr value{EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 65, &this->memory)}; EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()), 0); EXPECT_EQ(mgp_edge_set_property(edge.get(), "property", value.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT); } diff --git a/tests/unit/query_required_privileges.cpp b/tests/unit/query_required_privileges.cpp index 55ab02cb9..4bdef60dc 100644 --- a/tests/unit/query_required_privileges.cpp +++ b/tests/unit/query_required_privileges.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -70,7 +70,7 @@ TEST_F(TestPrivilegeExtractor, MatchNodeSetLabels) { TEST_F(TestPrivilegeExtractor, MatchNodeSetProperty) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), - SET(PROPERTY_LOOKUP(storage.Create<Identifier>("n"), PROP_0), LITERAL(42)))); + SET(PROPERTY_LOOKUP(dba, storage.Create<Identifier>("n"), PROP_0), LITERAL(42)))); EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::SET)); } @@ -88,8 +88,8 @@ TEST_F(TestPrivilegeExtractor, MatchNodeRemoveLabels) { } TEST_F(TestPrivilegeExtractor, MatchNodeRemoveProperty) { - auto *query = - QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP(storage.Create<Identifier>("n"), PROP_0)))); + auto *query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP(dba, storage.Create<Identifier>("n"), PROP_0)))); EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::REMOVE)); } diff --git a/tests/unit/query_semantic.cpp b/tests/unit/query_semantic.cpp index ee18380b2..0c75e7027 100644 --- a/tests/unit/query_semantic.cpp +++ b/tests/unit/query_semantic.cpp @@ -13,6 +13,7 @@ #include <sstream> #include <variant> +#include "disk_test_utils.hpp" #include "gtest/gtest.h" #include "query/exceptions.hpp" @@ -20,20 +21,34 @@ #include "query/frontend/semantic/symbol_generator.hpp" #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/preprocess.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "query_common.hpp" using namespace memgraph::query; +template <typename StorageType> class TestSymbolGenerator : public ::testing::Test { protected: - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + const std::string testSuite = "query_semantic"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } }; -TEST_F(TestSymbolGenerator, MatchNodeReturn) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(TestSymbolGenerator, StorageTypes); + +TYPED_TEST(TestSymbolGenerator, MatchNodeReturn) { // MATCH (node_atom_1) RETURN node_atom_1 auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("node_atom_1"))), RETURN("node_atom_1"))); auto symbol_table = memgraph::query::MakeSymbolTable(query_ast); @@ -57,7 +72,7 @@ TEST_F(TestSymbolGenerator, MatchNodeReturn) { EXPECT_EQ(node_sym, ret_sym); } -TEST_F(TestSymbolGenerator, MatchNamedPattern) { +TYPED_TEST(TestSymbolGenerator, MatchNamedPattern) { // MATCH p = (node_atom_1) RETURN node_atom_1 auto query_ast = QUERY(SINGLE_QUERY(MATCH(NAMED_PATTERN("p", NODE("node_atom_1"))), RETURN("p"))); auto symbol_table = memgraph::query::MakeSymbolTable(query_ast); @@ -71,7 +86,7 @@ TEST_F(TestSymbolGenerator, MatchNamedPattern) { EXPECT_TRUE(pattern_sym.user_declared()); } -TEST_F(TestSymbolGenerator, MatchUnboundMultiReturn) { +TYPED_TEST(TestSymbolGenerator, MatchUnboundMultiReturn) { // AST using variable in return bound by naming the previous return // expression. This is treated as an unbound variable. // MATCH (node_atom_1) RETURN node_atom_1 AS n, n @@ -79,21 +94,21 @@ TEST_F(TestSymbolGenerator, MatchUnboundMultiReturn) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchNodeUnboundReturn) { +TYPED_TEST(TestSymbolGenerator, MatchNodeUnboundReturn) { // AST with unbound variable in return: MATCH (n) RETURN x auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("x"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError); } -TEST_F(TestSymbolGenerator, CreatePropertyUnbound) { +TYPED_TEST(TestSymbolGenerator, CreatePropertyUnbound) { // AST with unbound variable in create: CREATE ({prop: x}) auto node = NODE("anon"); - std::get<0>(node->properties_)[storage.GetPropertyIx("prop")] = IDENT("x"); + std::get<0>(node->properties_)[this->storage.GetPropertyIx("prop")] = IDENT("x"); auto *query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(node)))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), UnboundVariableError); } -TEST_F(TestSymbolGenerator, CreateNodeReturn) { +TYPED_TEST(TestSymbolGenerator, CreateNodeReturn) { // Simple AST returning a created node: CREATE (n) RETURN n auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query_ast); @@ -114,26 +129,26 @@ TEST_F(TestSymbolGenerator, CreateNodeReturn) { EXPECT_EQ(node_sym, ret_sym); } -TEST_F(TestSymbolGenerator, CreateRedeclareNode) { +TYPED_TEST(TestSymbolGenerator, CreateRedeclareNode) { // AST with redeclaring a variable when creating nodes: CREATE (n), (n) auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("n"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MultiCreateRedeclareNode) { +TYPED_TEST(TestSymbolGenerator, MultiCreateRedeclareNode) { // AST with redeclaring a variable when creating nodes with multiple creates: // CREATE (n) CREATE (n) auto query_ast = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MatchCreateRedeclareNode) { +TYPED_TEST(TestSymbolGenerator, MatchCreateRedeclareNode) { // AST with redeclaring a match node variable in create: MATCH (n) CREATE (n) auto query_ast = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CREATE(PATTERN(NODE("n"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query_ast), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MatchCreateRedeclareEdge) { +TYPED_TEST(TestSymbolGenerator, MatchCreateRedeclareEdge) { // AST with redeclaring a match edge variable in create: // MATCH (n) -[r]- (m) CREATE (n) -[r :relationship]-> (l) auto relationship = "relationship"; @@ -143,14 +158,14 @@ TEST_F(TestSymbolGenerator, MatchCreateRedeclareEdge) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MatchTypeMismatch) { +TYPED_TEST(TestSymbolGenerator, MatchTypeMismatch) { // Using an edge variable as a node causes a type mismatch. // MATCH (n) -[r]-> (r) auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("r"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), TypeMismatchError); } -TEST_F(TestSymbolGenerator, MatchCreateTypeMismatch) { +TYPED_TEST(TestSymbolGenerator, MatchCreateTypeMismatch) { // Using an edge variable as a node causes a type mismatch. // MATCH (n1) -[r1]- (n2) CREATE (r1) -[r2]-> (n2) auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n1"), EDGE("r1"), NODE("n2"))), @@ -158,18 +173,18 @@ TEST_F(TestSymbolGenerator, MatchCreateTypeMismatch) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), TypeMismatchError); } -TEST_F(TestSymbolGenerator, CreateMultipleEdgeType) { +TYPED_TEST(TestSymbolGenerator, CreateMultipleEdgeType) { // Multiple edge relationship are not allowed when creating edges. // CREATE (n) -[r :rel1 | :rel2]-> (m) auto rel1 = "rel1"; auto rel2 = "rel2"; auto edge = EDGE("r", EdgeAtom::Direction::OUT, {rel1}); - edge->edge_types_.emplace_back(storage.GetEdgeTypeIx(rel2)); + edge->edge_types_.emplace_back(this->storage.GetEdgeTypeIx(rel2)); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), edge, NODE("m"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CreateBidirectionalEdge) { +TYPED_TEST(TestSymbolGenerator, CreateBidirectionalEdge) { // Bidirectional relationships are not allowed when creating edges. // CREATE (n) -[r :rel1]- (m) auto rel1 = "rel1"; @@ -177,13 +192,13 @@ TEST_F(TestSymbolGenerator, CreateBidirectionalEdge) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchWhereUnbound) { +TYPED_TEST(TestSymbolGenerator, MatchWhereUnbound) { // Test MATCH (n) WHERE missing < 42 RETURN n auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WHERE(LESS(IDENT("missing"), LITERAL(42))), RETURN("n"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, CreateDelete) { +TYPED_TEST(TestSymbolGenerator, CreateDelete) { // Test CREATE (n) DELETE n auto node = NODE("n"); auto ident = IDENT("n"); @@ -197,13 +212,13 @@ TEST_F(TestSymbolGenerator, CreateDelete) { EXPECT_EQ(node_symbol, ident_symbol); } -TEST_F(TestSymbolGenerator, CreateDeleteUnbound) { +TYPED_TEST(TestSymbolGenerator, CreateDeleteUnbound) { // Test CREATE (n) DELETE missing auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), DELETE(IDENT("missing")))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchWithReturn) { +TYPED_TEST(TestSymbolGenerator, MatchWithReturn) { // Test MATCH (old) WITH old AS n RETURN n AS n auto node = NODE("old"); auto old_ident = IDENT("old"); @@ -225,19 +240,19 @@ TEST_F(TestSymbolGenerator, MatchWithReturn) { EXPECT_NE(n, ret_n); } -TEST_F(TestSymbolGenerator, MatchWithReturnUnbound) { +TYPED_TEST(TestSymbolGenerator, MatchWithReturnUnbound) { // Test MATCH (old) WITH old AS n RETURN old auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("n")), RETURN("old"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchWithWhere) { +TYPED_TEST(TestSymbolGenerator, MatchWithWhere) { // Test MATCH (old) WITH old AS n WHERE n.prop < 42 - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto node = NODE("old"); auto old_ident = IDENT("old"); auto with_as_n = AS("n"); - auto n_prop = PROPERTY_LOOKUP("n", prop); + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), WITH(old_ident, with_as_n), WHERE(LESS(n_prop, LITERAL(42))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); // symbols for pattern, `old` and `n` @@ -251,15 +266,15 @@ TEST_F(TestSymbolGenerator, MatchWithWhere) { EXPECT_EQ(n, with_n); } -TEST_F(TestSymbolGenerator, MatchWithWhereUnbound) { +TYPED_TEST(TestSymbolGenerator, MatchWithWhereUnbound) { // Test MATCH (old) WITH COUNT(old) AS c WHERE old.prop < 42 - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old"), false), AS("c")), - WHERE(LESS(PROPERTY_LOOKUP("old", prop), LITERAL(42))))); + WHERE(LESS(PROPERTY_LOOKUP(this->dba, "old", prop), LITERAL(42))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, CreateMultiExpand) { +TYPED_TEST(TestSymbolGenerator, CreateMultiExpand) { // Test CREATE (n) -[r :r]-> (m), (n) - [p :p]-> (l) auto r_type = "r"; auto p_type = "p"; @@ -291,7 +306,7 @@ TEST_F(TestSymbolGenerator, CreateMultiExpand) { EXPECT_NE(r, p); } -TEST_F(TestSymbolGenerator, MatchCreateExpandLabel) { +TYPED_TEST(TestSymbolGenerator, MatchCreateExpandLabel) { // Test MATCH (n) CREATE (m) -[r :r]-> (n:label) auto r_type = "r"; auto label = "label"; @@ -301,20 +316,20 @@ TEST_F(TestSymbolGenerator, MatchCreateExpandLabel) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CreateExpandProperty) { +TYPED_TEST(TestSymbolGenerator, CreateExpandProperty) { // Test CREATE (n) -[r :r]-> (n {prop: 42}) auto r_type = "r"; auto n_prop = NODE("n"); - std::get<0>(n_prop->properties_)[storage.GetPropertyIx("prop")] = LITERAL(42); + std::get<0>(n_prop->properties_)[this->storage.GetPropertyIx("prop")] = LITERAL(42); auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {r_type}), n_prop)))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchReturnSum) { +TYPED_TEST(TestSymbolGenerator, MatchReturnSum) { // Test MATCH (n) RETURN SUM(n.prop) + 42 AS result - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto node = NODE("n"); - auto sum = SUM(PROPERTY_LOOKUP("n", prop), false); + auto sum = SUM(PROPERTY_LOOKUP(this->dba, "n", prop), false); auto as_result = AS("result"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(ADD(sum, LITERAL(42)), as_result))); auto symbol_table = memgraph::query::MakeSymbolTable(query); @@ -328,30 +343,30 @@ TEST_F(TestSymbolGenerator, MatchReturnSum) { EXPECT_NE(result_symbol, sum_symbol); } -TEST_F(TestSymbolGenerator, NestedAggregation) { +TYPED_TEST(TestSymbolGenerator, NestedAggregation) { // Test MATCH (n) RETURN SUM(42 + SUM(n.prop)) AS s - auto prop = dba.NameToProperty("prop"); - auto query = - QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), - RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP("n", prop), false)), false), AS("s")))); + auto prop = this->dba.NameToProperty("prop"); + auto query = QUERY( + SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), + RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP(this->dba, "n", prop), false)), false), AS("s")))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, WrongAggregationContext) { +TYPED_TEST(TestSymbolGenerator, WrongAggregationContext) { // Test MATCH (n) WITH n.prop AS prop WHERE SUM(prop) < 42 - auto prop = dba.NameToProperty("prop"); - auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(PROPERTY_LOOKUP("n", prop), AS("prop")), + auto prop = this->dba.NameToProperty("prop"); + auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(PROPERTY_LOOKUP(this->dba, "n", prop), AS("prop")), WHERE(LESS(SUM(IDENT("prop"), false), LITERAL(42))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchPropCreateNodeProp) { +TYPED_TEST(TestSymbolGenerator, MatchPropCreateNodeProp) { // Test MATCH (n) CREATE (m {prop: n.prop}) - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(this->dba, "prop"); auto node_n = NODE("n"); auto node_m = NODE("m"); - auto n_prop = PROPERTY_LOOKUP("n", prop.second); - std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second); + std::get<0>(node_m->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), CREATE(PATTERN(node_m)))); auto symbol_table = memgraph::query::MakeSymbolTable(query); // symbols: pattern * 2, `node_n`, `node_m` @@ -362,7 +377,7 @@ TEST_F(TestSymbolGenerator, MatchPropCreateNodeProp) { EXPECT_NE(n, m); } -TEST_F(TestSymbolGenerator, CreateNodeEdge) { +TYPED_TEST(TestSymbolGenerator, CreateNodeEdge) { // Test CREATE (n), (n) -[r :r]-> (n) auto r_type = "r"; auto node_1 = NODE("n"); @@ -379,7 +394,7 @@ TEST_F(TestSymbolGenerator, CreateNodeEdge) { EXPECT_NE(n, symbol_table.at(*edge->identifier_)); } -TEST_F(TestSymbolGenerator, MatchWithCreate) { +TYPED_TEST(TestSymbolGenerator, MatchWithCreate) { // Test MATCH (n) WITH n AS m CREATE (m) -[r :r]-> (m) auto r_type = "r"; auto node_1 = NODE("n"); @@ -399,51 +414,51 @@ TEST_F(TestSymbolGenerator, MatchWithCreate) { EXPECT_EQ(m, symbol_table.at(*node_3->identifier_)); } -TEST_F(TestSymbolGenerator, SameResultsWith) { +TYPED_TEST(TestSymbolGenerator, SameResultsWith) { // Test MATCH (n) WITH n AS m, n AS m auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("m"), "n", AS("m")))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, SameResults) { +TYPED_TEST(TestSymbolGenerator, SameResults) { // Test MATCH (n) RETURN n, n auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", "n"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, SkipUsingIdentifier) { +TYPED_TEST(TestSymbolGenerator, SkipUsingIdentifier) { // Test MATCH (old) WITH old AS new SKIP old auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new"), SKIP(IDENT("old"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, SkipUsingIdentifierAlias) { +TYPED_TEST(TestSymbolGenerator, SkipUsingIdentifierAlias) { // Test MATCH (old) WITH old AS new SKIP new auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH("old", AS("new"), SKIP(IDENT("new"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, LimitUsingIdentifier) { +TYPED_TEST(TestSymbolGenerator, LimitUsingIdentifier) { // Test MATCH (n) RETURN n AS n LIMIT n auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n", LIMIT(IDENT("n"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, OrderByAggregation) { +TYPED_TEST(TestSymbolGenerator, OrderByAggregation) { // Test MATCH (old) RETURN old AS new ORDER BY COUNT(1) auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1), false))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, OrderByUnboundVariable) { +TYPED_TEST(TestSymbolGenerator, OrderByUnboundVariable) { // Test MATCH (old) RETURN COUNT(old) AS new ORDER BY old auto query = QUERY( SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN(COUNT(IDENT("old"), false), AS("new"), ORDER_BY(IDENT("old"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, AggregationOrderBy) { +TYPED_TEST(TestSymbolGenerator, AggregationOrderBy) { // Test MATCH (old) RETURN COUNT(old) AS new ORDER BY new auto node = NODE("old"); auto ident_old = IDENT("old"); @@ -460,7 +475,7 @@ TEST_F(TestSymbolGenerator, AggregationOrderBy) { EXPECT_EQ(new_sym, symbol_table.at(*ident_new)); } -TEST_F(TestSymbolGenerator, OrderByOldVariable) { +TYPED_TEST(TestSymbolGenerator, OrderByOldVariable) { // Test MATCH (old) RETURN old AS new ORDER BY old auto node = NODE("old"); auto ident_old = IDENT("old"); @@ -477,13 +492,13 @@ TEST_F(TestSymbolGenerator, OrderByOldVariable) { EXPECT_NE(old, new_sym); } -TEST_F(TestSymbolGenerator, MergeVariableError) { +TYPED_TEST(TestSymbolGenerator, MergeVariableError) { // Test MATCH (n) MERGE (n) auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), MERGE(PATTERN(NODE("n"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MergeVariableErrorEdge) { +TYPED_TEST(TestSymbolGenerator, MergeVariableErrorEdge) { // Test MATCH (n) -[r]- (m) MERGE (a) -[r :rel]- (b) auto rel = "rel"; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))), @@ -491,24 +506,24 @@ TEST_F(TestSymbolGenerator, MergeVariableErrorEdge) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, MergeEdgeWithoutType) { +TYPED_TEST(TestSymbolGenerator, MergeEdgeWithoutType) { // Test MERGE (a) -[r]- (b) auto query = QUERY(SINGLE_QUERY(MERGE(PATTERN(NODE("a"), EDGE("r"), NODE("b"))))); // Edge must have a type, since it doesn't we raise. EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MergeOnMatchOnCreate) { +TYPED_TEST(TestSymbolGenerator, MergeOnMatchOnCreate) { // Test MATCH (n) MERGE (n) -[r :rel]- (m) ON MATCH SET n.prop = 42 // ON CREATE SET m.prop = 42 RETURN r AS r auto rel = "rel"; - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto match_n = NODE("n"); auto merge_n = NODE("n"); auto edge_r = EDGE("r", EdgeAtom::Direction::BOTH, {rel}); auto node_m = NODE("m"); - auto n_prop = PROPERTY_LOOKUP("n", prop); - auto m_prop = PROPERTY_LOOKUP("m", prop); + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop); + auto m_prop = PROPERTY_LOOKUP(this->dba, "m", prop); auto ident_r = IDENT("r"); auto as_r = AS("r"); auto query = QUERY(SINGLE_QUERY( @@ -532,14 +547,14 @@ TEST_F(TestSymbolGenerator, MergeOnMatchOnCreate) { EXPECT_EQ(m, symbol_table.at(*dynamic_cast<Identifier *>(m_prop->expression_))); } -TEST_F(TestSymbolGenerator, WithUnwindRedeclareReturn) { +TYPED_TEST(TestSymbolGenerator, WithUnwindRedeclareReturn) { // Test WITH [1, 2] AS list UNWIND list AS list RETURN list auto query = QUERY( SINGLE_QUERY(WITH(LIST(LITERAL(1), LITERAL(2)), AS("list")), UNWIND(IDENT("list"), AS("list")), RETURN("list"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, WithUnwindReturn) { +TYPED_TEST(TestSymbolGenerator, WithUnwindReturn) { // WITH [1, 2] AS list UNWIND list AS elem RETURN list AS list, elem AS elem auto with_as_list = AS("list"); auto unwind = UNWIND(IDENT("list"), AS("elem")); @@ -562,15 +577,15 @@ TEST_F(TestSymbolGenerator, WithUnwindReturn) { EXPECT_NE(elem, symbol_table.at(*ret_as_elem)); } -TEST_F(TestSymbolGenerator, MatchCrossReferenceVariable) { +TYPED_TEST(TestSymbolGenerator, MatchCrossReferenceVariable) { // MATCH (n {prop: m.prop}), (m {prop: n.prop}) RETURN n - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(this->dba, "prop"); auto node_n = NODE("n"); - auto m_prop = PROPERTY_LOOKUP("m", prop.second); - std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = m_prop; + auto m_prop = PROPERTY_LOOKUP(this->dba, "m", prop.second); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(prop.first)] = m_prop; auto node_m = NODE("m"); - auto n_prop = PROPERTY_LOOKUP("n", prop.second); - std::get<0>(node_m->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second); + std::get<0>(node_m->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop; auto ident_n = IDENT("n"); auto as_n = AS("n"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n), PATTERN(node_m)), RETURN(ident_n, as_n))); @@ -587,16 +602,16 @@ TEST_F(TestSymbolGenerator, MatchCrossReferenceVariable) { EXPECT_NE(m, symbol_table.at(*as_n)); } -TEST_F(TestSymbolGenerator, MatchWithAsteriskReturnAsterisk) { +TYPED_TEST(TestSymbolGenerator, MatchWithAsteriskReturnAsterisk) { // MATCH (n) -[e]- (m) WITH * RETURN *, n.prop - auto prop = dba.NameToProperty("prop"); - auto n_prop = PROPERTY_LOOKUP("n", prop); + auto prop = this->dba.NameToProperty("prop"); + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop); auto ret = RETURN(n_prop, AS("n.prop")); ret->body_.all_identifiers = true; auto node_n = NODE("n"); auto edge = EDGE("e"); auto node_m = NODE("m"); - auto with = storage.Create<With>(); + auto with = this->storage.template Create<With>(); with->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, node_m)), with, ret)); auto symbol_table = memgraph::query::MakeSymbolTable(query); @@ -606,7 +621,7 @@ TEST_F(TestSymbolGenerator, MatchWithAsteriskReturnAsterisk) { EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_))); } -TEST_F(TestSymbolGenerator, MatchReturnAsteriskSameResult) { +TYPED_TEST(TestSymbolGenerator, MatchReturnAsteriskSameResult) { // MATCH (n) RETURN *, n auto ret = RETURN("n"); ret->body_.all_identifiers = true; @@ -614,17 +629,17 @@ TEST_F(TestSymbolGenerator, MatchReturnAsteriskSameResult) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchReturnAsteriskNoUserVariables) { +TYPED_TEST(TestSymbolGenerator, MatchReturnAsteriskNoUserVariables) { // MATCH () RETURN * - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; - auto ident_n = storage.Create<Identifier>("anon", false); - auto node = storage.Create<NodeAtom>(ident_n); + auto ident_n = this->storage.template Create<Identifier>("anon", false); + auto node = this->storage.template Create<NodeAtom>(ident_n); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), ret)); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchMergeExpandLabel) { +TYPED_TEST(TestSymbolGenerator, MatchMergeExpandLabel) { // Test MATCH (n) MERGE (m) -[r :r]-> (n:label) auto r_type = "r"; auto label = "label"; @@ -634,12 +649,12 @@ TEST_F(TestSymbolGenerator, MatchMergeExpandLabel) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) { +TYPED_TEST(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) { // Test MATCH (n) -[r {prop: n.prop}]- (m) RETURN r - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(this->dba, "prop"); auto edge = EDGE("r"); - auto n_prop = PROPERTY_LOOKUP("n", prop.second); - std::get<0>(edge->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second); + std::get<0>(edge->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop; auto node_n = NODE("n"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); @@ -649,11 +664,11 @@ TEST_F(TestSymbolGenerator, MatchEdgeWithIdentifierInProperty) { EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_))); } -TEST_F(TestSymbolGenerator, MatchVariablePathUsingIdentifier) { +TYPED_TEST(TestSymbolGenerator, MatchVariablePathUsingIdentifier) { // Test MATCH (n) -[r *..l.prop]- (m), (l) RETURN r - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto edge = EDGE_VARIABLE("r"); - auto l_prop = PROPERTY_LOOKUP("l", prop); + auto l_prop = PROPERTY_LOOKUP(this->dba, "l", prop); edge->upper_bound_ = l_prop; auto node_l = NODE("l"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m")), PATTERN(node_l)), RETURN("r"))); @@ -667,18 +682,18 @@ TEST_F(TestSymbolGenerator, MatchVariablePathUsingIdentifier) { EXPECT_EQ(r.type(), Symbol::Type::EDGE_LIST); } -TEST_F(TestSymbolGenerator, MatchVariablePathUsingUnboundIdentifier) { +TYPED_TEST(TestSymbolGenerator, MatchVariablePathUsingUnboundIdentifier) { // Test MATCH (n) -[r *..l.prop]- (m) MATCH (l) RETURN r - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto edge = EDGE_VARIABLE("r"); - auto l_prop = PROPERTY_LOOKUP("l", prop); + auto l_prop = PROPERTY_LOOKUP(this->dba, "l", prop); edge->upper_bound_ = l_prop; auto node_l = NODE("l"); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), MATCH(PATTERN(node_l)), RETURN("r"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CreateVariablePath) { +TYPED_TEST(TestSymbolGenerator, CreateVariablePath) { // Test CREATE (n) -[r *]-> (m) raises a SemanticException, since variable // paths cannot be created. auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT); @@ -686,7 +701,7 @@ TEST_F(TestSymbolGenerator, CreateVariablePath) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MergeVariablePath) { +TYPED_TEST(TestSymbolGenerator, MergeVariablePath) { // Test MERGE (n) -[r *]-> (m) raises a SemanticException, since variable // paths cannot be created. auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT); @@ -694,7 +709,7 @@ TEST_F(TestSymbolGenerator, MergeVariablePath) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, RedeclareVariablePath) { +TYPED_TEST(TestSymbolGenerator, RedeclareVariablePath) { // Test MATCH (n) -[n*]-> (m) RETURN n raises RedeclareVariableError. // This is just a temporary solution, before we add the support for using // variable paths with already declared symbols. In the future, this test @@ -704,32 +719,32 @@ TEST_F(TestSymbolGenerator, RedeclareVariablePath) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), RedeclareVariableError); } -TEST_F(TestSymbolGenerator, VariablePathSameIdentifier) { +TYPED_TEST(TestSymbolGenerator, VariablePathSameIdentifier) { // Test MATCH (n) -[r *r.prop..]-> (m) RETURN r raises UnboundVariableError. // `r` cannot be used inside the range expression, since it is bound by the // variable expansion itself. - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto edge = EDGE_VARIABLE("r", EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::OUT); - edge->lower_bound_ = PROPERTY_LOOKUP("r", prop); + edge->lower_bound_ = PROPERTY_LOOKUP(this->dba, "r", prop); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchPropertySameIdentifier) { +TYPED_TEST(TestSymbolGenerator, MatchPropertySameIdentifier) { // Test MATCH (n {prop: n.prop}) RETURN n // Using `n.prop` needs to work, because filters are run after the value for // matched symbol is obtained. - auto prop = PROPERTY_PAIR("prop"); + auto prop = PROPERTY_PAIR(this->dba, "prop"); auto node_n = NODE("n"); - auto n_prop = PROPERTY_LOOKUP("n", prop.second); - std::get<0>(node_n->properties_)[storage.GetPropertyIx(prop.first)] = n_prop; + auto n_prop = PROPERTY_LOOKUP(this->dba, "n", prop.second); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx(prop.first)] = n_prop; auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n)), RETURN("n"))); auto symbol_table = memgraph::query::MakeSymbolTable(query); auto n = symbol_table.at(*node_n->identifier_); EXPECT_EQ(n, symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_))); } -TEST_F(TestSymbolGenerator, WithReturnAll) { +TYPED_TEST(TestSymbolGenerator, WithReturnAll) { // Test WITH 42 AS x RETURN all(x IN [x] WHERE x = 2) AS x, x AS y auto *with_as_x = AS("x"); auto *list_x = IDENT("x"); @@ -751,7 +766,7 @@ TEST_F(TestSymbolGenerator, WithReturnAll) { EXPECT_NE(symbol_table.at(*all->identifier_), symbol_table.at(*ret_as_x)); } -TEST_F(TestSymbolGenerator, WithReturnSingle) { +TYPED_TEST(TestSymbolGenerator, WithReturnSingle) { // Test WITH 42 AS x RETURN single(x IN [x] WHERE x = 2) AS x, x AS y auto *with_as_x = AS("x"); auto *list_x = IDENT("x"); @@ -773,7 +788,7 @@ TEST_F(TestSymbolGenerator, WithReturnSingle) { EXPECT_NE(symbol_table.at(*single->identifier_), symbol_table.at(*ret_as_x)); } -TEST_F(TestSymbolGenerator, WithReturnReduce) { +TYPED_TEST(TestSymbolGenerator, WithReturnReduce) { // Test WITH 42 AS x RETURN reduce(y = 0, x IN [x] y + x) AS x, x AS y auto *with_as_x = AS("x"); auto *list_x = IDENT("x"); @@ -800,7 +815,7 @@ TEST_F(TestSymbolGenerator, WithReturnReduce) { EXPECT_NE(symbol_table.at(*reduce->accumulator_), symbol_table.at(*ret_as_y)); } -TEST_F(TestSymbolGenerator, WithReturnExtract) { +TYPED_TEST(TestSymbolGenerator, WithReturnExtract) { // Test WITH [1, 2, 3] AS x RETURN extract(x IN x | x + 1) AS x, x AS y auto *with_as_x = AS("x"); auto *list_x = IDENT("x"); @@ -825,14 +840,14 @@ TEST_F(TestSymbolGenerator, WithReturnExtract) { EXPECT_NE(symbol_table.at(*extract->identifier_), symbol_table.at(*ret_as_x)); } -TEST_F(TestSymbolGenerator, MatchBfsReturn) { +TYPED_TEST(TestSymbolGenerator, MatchBfsReturn) { // Test MATCH (n) -[r *bfs..n.prop] (r, n | r.prop)]-> (m) RETURN r AS r - auto prop = dba.NameToProperty("prop"); + auto prop = this->dba.NameToProperty("prop"); auto *node_n = NODE("n"); - auto *r_prop = PROPERTY_LOOKUP("r", prop); - auto *n_prop = PROPERTY_LOOKUP("n", prop); - auto *bfs = storage.Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT, - std::vector<EdgeTypeIx>{}); + auto *r_prop = PROPERTY_LOOKUP(this->dba, "r", prop); + auto *n_prop = PROPERTY_LOOKUP(this->dba, "n", prop); + auto *bfs = this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, + EdgeAtom::Direction::OUT, std::vector<EdgeTypeIx>{}); bfs->filter_lambda_.inner_edge = IDENT("r"); bfs->filter_lambda_.inner_node = IDENT("n"); bfs->filter_lambda_.expression = r_prop; @@ -852,9 +867,10 @@ TEST_F(TestSymbolGenerator, MatchBfsReturn) { EXPECT_EQ(symbol_table.at(*node_n->identifier_), symbol_table.at(*dynamic_cast<Identifier *>(n_prop->expression_))); } -TEST_F(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) { +TYPED_TEST(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) { // Test MATCH (n) -[r *bfs..10 (e, n | r)]-> (m) RETURN r - auto *bfs = storage.Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); + auto *bfs = + this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); bfs->filter_lambda_.inner_edge = IDENT("e"); bfs->filter_lambda_.inner_node = IDENT("n"); bfs->filter_lambda_.expression = IDENT("r"); @@ -863,10 +879,11 @@ TEST_F(TestSymbolGenerator, MatchBfsUsesEdgeSymbolError) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) { +TYPED_TEST(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) { // Test MATCH (a) -[r *bfs..10 (e, n | a)]-> (m) RETURN r auto *node_a = NODE("a"); - auto *bfs = storage.Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); + auto *bfs = + this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); bfs->filter_lambda_.inner_edge = IDENT("e"); bfs->filter_lambda_.inner_node = IDENT("n"); bfs->filter_lambda_.expression = IDENT("a"); @@ -877,9 +894,10 @@ TEST_F(TestSymbolGenerator, MatchBfsUsesPreviousOuterSymbol) { symbol_table.at(*dynamic_cast<Identifier *>(bfs->filter_lambda_.expression))); } -TEST_F(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) { +TYPED_TEST(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) { // Test MATCH (n) -[r *bfs..10 (e, n | m)]-> (m) RETURN r - auto *bfs = storage.Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); + auto *bfs = + this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, EdgeAtom::Direction::OUT); bfs->filter_lambda_.inner_edge = IDENT("e"); bfs->filter_lambda_.inner_node = IDENT("n"); bfs->filter_lambda_.expression = IDENT("m"); @@ -888,15 +906,15 @@ TEST_F(TestSymbolGenerator, MatchBfsUsesLaterSymbolError) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, MatchVariableLambdaSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchVariableLambdaSymbols) { // MATCH ()-[*]-() RETURN 42 AS res - auto ident_n = storage.Create<Identifier>("anon_n", false); - auto node = storage.Create<NodeAtom>(ident_n); - auto edge = storage.Create<EdgeAtom>(storage.Create<Identifier>("anon_r", false), EdgeAtom::Type::DEPTH_FIRST, - EdgeAtom::Direction::BOTH); - edge->filter_lambda_.inner_edge = storage.Create<Identifier>("anon_inner_e", false); - edge->filter_lambda_.inner_node = storage.Create<Identifier>("anon_inner_n", false); - auto end_node = storage.Create<NodeAtom>(storage.Create<Identifier>("anon_end", false)); + auto ident_n = this->storage.template Create<Identifier>("anon_n", false); + auto node = this->storage.template Create<NodeAtom>(ident_n); + auto edge = this->storage.template Create<EdgeAtom>(this->storage.template Create<Identifier>("anon_r", false), + EdgeAtom::Type::DEPTH_FIRST, EdgeAtom::Direction::BOTH); + edge->filter_lambda_.inner_edge = this->storage.template Create<Identifier>("anon_inner_e", false); + edge->filter_lambda_.inner_node = this->storage.template Create<Identifier>("anon_inner_n", false); + auto end_node = this->storage.template Create<NodeAtom>(this->storage.template Create<Identifier>("anon_end", false)); auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node, edge, end_node)), RETURN(LITERAL(42), AS("res")))); auto symbol_table = memgraph::query::MakeSymbolTable(query); // Symbols for `anon_n`, `anon_r`, `anon_inner_e`, `anon_inner_n`, `anon_end` @@ -912,16 +930,16 @@ TEST_F(TestSymbolGenerator, MatchVariableLambdaSymbols) { } } -TEST_F(TestSymbolGenerator, MatchWShortestReturn) { +TYPED_TEST(TestSymbolGenerator, MatchWShortestReturn) { // Test MATCH (n) -[r *wShortest (r, n | r.weight) (r, n | r.filter)]-> (m) // RETURN r AS r - auto weight = dba.NameToProperty("weight"); - auto filter = dba.NameToProperty("filter"); + auto weight = this->dba.NameToProperty("weight"); + auto filter = this->dba.NameToProperty("filter"); auto *node_n = NODE("n"); - auto *r_weight = PROPERTY_LOOKUP("r", weight); - auto *r_filter = PROPERTY_LOOKUP("r", filter); - auto *shortest = storage.Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, - EdgeAtom::Direction::OUT, std::vector<EdgeTypeIx>{}); + auto *r_weight = PROPERTY_LOOKUP(this->dba, "r", weight); + auto *r_filter = PROPERTY_LOOKUP(this->dba, "r", filter); + auto *shortest = this->storage.template Create<EdgeAtom>(IDENT("r"), EdgeAtom::Type::WEIGHTED_SHORTEST_PATH, + EdgeAtom::Direction::OUT, std::vector<EdgeTypeIx>{}); { shortest->weight_lambda_.inner_edge = IDENT("r"); shortest->weight_lambda_.inner_node = IDENT("n"); @@ -954,14 +972,14 @@ TEST_F(TestSymbolGenerator, MatchWShortestReturn) { EXPECT_TRUE(symbol_table.at(*shortest->filter_lambda_.inner_node).user_declared()); } -TEST_F(TestSymbolGenerator, MatchUnionSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionSymbols) { // RETURN 5 as X UNION RETURN 6 AS x auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))), UNION(SINGLE_QUERY(RETURN(LITERAL(6), AS("X"))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); EXPECT_EQ(symbol_table.max_position(), 3); } -TEST_F(TestSymbolGenerator, MatchUnionMultipleSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionMultipleSymbols) { // RETURN 5 as X, 6 AS Y UNION RETURN 5 AS Y, 6 AS x auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"), LITERAL(6), AS("Y"))), UNION(SINGLE_QUERY(RETURN(LITERAL(5), AS("Y"), LITERAL(6), AS("X"))))); @@ -969,14 +987,14 @@ TEST_F(TestSymbolGenerator, MatchUnionMultipleSymbols) { EXPECT_EQ(symbol_table.max_position(), 6); } -TEST_F(TestSymbolGenerator, MatchUnionAllSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionAllSymbols) { // RETURN 5 as X UNION ALL RETURN 6 AS x auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"))), UNION_ALL(SINGLE_QUERY(RETURN(LITERAL(6), AS("X"))))); auto symbol_table = memgraph::query::MakeSymbolTable(query); EXPECT_EQ(symbol_table.max_position(), 3); } -TEST_F(TestSymbolGenerator, MatchUnionAllMultipleSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionAllMultipleSymbols) { // RETURN 5 as X, 6 AS Y UNION ALL RETURN 5 AS Y, 6 AS x auto query = QUERY(SINGLE_QUERY(RETURN(LITERAL(5), AS("X"), LITERAL(6), AS("Y"))), UNION_ALL(SINGLE_QUERY(RETURN(LITERAL(5), AS("Y"), LITERAL(6), AS("X"))))); @@ -984,9 +1002,9 @@ TEST_F(TestSymbolGenerator, MatchUnionAllMultipleSymbols) { EXPECT_EQ(symbol_table.max_position(), 6); } -TEST_F(TestSymbolGenerator, MatchUnionReturnAllSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionReturnAllSymbols) { // WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS X, 4 AS Y - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret), UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("X"), LITERAL(4), AS("Y"))))); @@ -994,7 +1012,7 @@ TEST_F(TestSymbolGenerator, MatchUnionReturnAllSymbols) { EXPECT_EQ(symbol_table.max_position(), 6); } -TEST_F(TestSymbolGenerator, MatchUnionReturnSymbols) { +TYPED_TEST(TestSymbolGenerator, MatchUnionReturnSymbols) { // WITH 1 as X, 2 AS Y RETURN Y, X UNION RETURN 3 AS X, 4 AS Y auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), RETURN("Y", "X")), UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("X"), LITERAL(4), AS("Y"))))); @@ -1002,27 +1020,27 @@ TEST_F(TestSymbolGenerator, MatchUnionReturnSymbols) { EXPECT_EQ(symbol_table.max_position(), 8); } -TEST_F(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticExpcetion) { +TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticExpcetion) { // WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS Z, 4 AS Y - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret), UNION(SINGLE_QUERY(RETURN(LITERAL(3), AS("Z"), LITERAL(4), AS("Y"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticExpcetion) { +TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticExpcetion) { // WITH 1 as X, 2 AS Y RETURN * UNION RETURN 4 AS Y - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(1), AS("X"), LITERAL(2), AS("Y")), ret), UNION(SINGLE_QUERY(RETURN(LITERAL(4), AS("Y"))))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, MatchUnion) { +TYPED_TEST(TestSymbolGenerator, MatchUnion) { // WITH 5 AS X, 3 AS Y RETURN * UNION WITH 9 AS Y, 4 AS X RETURN Y, X - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(WITH(LITERAL(5), AS("X"), LITERAL(3), AS("Y")), ret), UNION(SINGLE_QUERY(WITH(LITERAL(9), AS("Y"), LITERAL(4), AS("X")), RETURN("Y", "X")))); @@ -1030,9 +1048,9 @@ TEST_F(TestSymbolGenerator, MatchUnion) { EXPECT_EQ(symbol_table.max_position(), 8); } -TEST_F(TestSymbolGenerator, CallProcedureYield) { +TYPED_TEST(TestSymbolGenerator, CallProcedureYield) { // WITH 1 AS x CALL proc(x) YIELD x AS y RETURN x, y - auto call = storage.Create<CallProcedure>(); + auto call = this->storage.template Create<CallProcedure>(); call->procedure_name_ = "proc"; auto *arg_x = IDENT("x"); call->arguments_.push_back(arg_x); @@ -1056,9 +1074,9 @@ TEST_F(TestSymbolGenerator, CallProcedureYield) { EXPECT_NE(symbol_table.at(*ret->body_.named_expressions[1]), sym_y); } -TEST_F(TestSymbolGenerator, CallProcedureShadowingYield) { +TYPED_TEST(TestSymbolGenerator, CallProcedureShadowingYield) { // WITH 1 AS x CALL proc() YIELD x RETURN 42 AS res - auto call = storage.Create<CallProcedure>(); + auto call = this->storage.template Create<CallProcedure>(); call->procedure_name_ = "proc"; call->result_fields_.emplace_back("x"); call->result_identifiers_.push_back(IDENT("x")); @@ -1066,9 +1084,9 @@ TEST_F(TestSymbolGenerator, CallProcedureShadowingYield) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CallProcedureShadowingYieldAlias) { +TYPED_TEST(TestSymbolGenerator, CallProcedureShadowingYieldAlias) { // WITH 1 AS x CALL proc() YIELD y AS x RETURN 42 AS res - auto call = storage.Create<CallProcedure>(); + auto call = this->storage.template Create<CallProcedure>(); call->procedure_name_ = "proc"; call->result_fields_.emplace_back("y"); call->result_identifiers_.push_back(IDENT("x")); @@ -1076,20 +1094,20 @@ TEST_F(TestSymbolGenerator, CallProcedureShadowingYieldAlias) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CallProcedureUnboundArgument) { +TYPED_TEST(TestSymbolGenerator, CallProcedureUnboundArgument) { // CALL proc(unbound) - auto call = storage.Create<CallProcedure>(); + auto call = this->storage.template Create<CallProcedure>(); call->procedure_name_ = "proc"; call->arguments_.push_back(IDENT("unbound")); auto query = QUERY(SINGLE_QUERY(call)); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); } -TEST_F(TestSymbolGenerator, CallWithoutFieldsReturnAsterisk) { +TYPED_TEST(TestSymbolGenerator, CallWithoutFieldsReturnAsterisk) { // CALL proc() RETURN * - auto call = storage.Create<CallProcedure>(); + auto call = this->storage.template Create<CallProcedure>(); call->procedure_name_ = "proc"; - auto ret = storage.Create<Return>(); + auto ret = this->storage.template Create<Return>(); ret->body_.all_identifiers = true; auto query = QUERY(SINGLE_QUERY(call, ret)); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException); @@ -1110,7 +1128,7 @@ TEST(TestSymbolTable, CreateAnonymousSymbolWithExistingUserSymbolCalledAnon) { ASSERT_EQ(anon2.name_, "anon2"); } -TEST_F(TestSymbolGenerator, PredefinedIdentifiers) { +TYPED_TEST(TestSymbolGenerator, PredefinedIdentifiers) { auto *first_op = IDENT("first_op", false); auto *second_op = IDENT("second_op", false); // RETURN first_op + second_op AS result @@ -1155,13 +1173,13 @@ TEST_F(TestSymbolGenerator, PredefinedIdentifiers) { // UNWIND first_op as u CREATE(first_op {prop: u}) auto unwind = UNWIND(first_op, AS("u")); auto node = NODE("first_op"); - std::get<0>(node->properties_)[storage.GetPropertyIx("prop")] = + std::get<0>(node->properties_)[this->storage.GetPropertyIx("prop")] = dynamic_cast<Identifier *>(unwind->named_expression_->expression_); query = QUERY(SINGLE_QUERY(unwind, CREATE(PATTERN(node)))); ASSERT_THROW(memgraph::query::MakeSymbolTable(query, {first_op}), SemanticException); } -TEST_F(TestSymbolGenerator, Foreach) { +TYPED_TEST(TestSymbolGenerator, Foreach) { auto *i = NEXPR("i", IDENT("i")); auto query = QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), RETURN("n"))); EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); @@ -1181,7 +1199,7 @@ TEST_F(TestSymbolGenerator, Foreach) { EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError); } -TEST_F(TestSymbolGenerator, Exists) { +TYPED_TEST(TestSymbolGenerator, Exists) { auto query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"))), WHERE(EXISTS(PATTERN(NODE("n"), EDGE("", EdgeAtom::Direction::BOTH, {}, false), NODE("m")))), RETURN("n"))); @@ -1215,7 +1233,7 @@ TEST_F(TestSymbolGenerator, Exists) { ASSERT_EQ(symbol.name_, "n"); } -TEST_F(TestSymbolGenerator, Subqueries) { +TYPED_TEST(TestSymbolGenerator, Subqueries) { // MATCH (n) CALL { MATCH (n) RETURN n } RETURN n // Yields exception because n in subquery is referenced in outer scope auto subquery = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n"))); diff --git a/tests/unit/query_streams.cpp b/tests/unit/query_streams.cpp index 3d080f8f0..3c78b5590 100644 --- a/tests/unit/query_streams.cpp +++ b/tests/unit/query_streams.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,13 +16,15 @@ #include <gtest/gtest.h> +#include "disk_test_utils.hpp" #include "integrations/constants.hpp" #include "integrations/kafka/exceptions.hpp" #include "kafka_mock.hpp" #include "query/config.hpp" #include "query/interpreter.hpp" #include "query/stream/streams.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "test_utils.hpp" using Streams = memgraph::query::stream::Streams; @@ -49,12 +51,22 @@ std::filesystem::path GetCleanDataDirectory() { } } // namespace -class StreamsTest : public ::testing::Test { +// We need this proxy class because we can't make template class a friend in different file. +class StreamsTest { public: - StreamsTest() { ResetStreamsObject(); } + std::optional<Streams> streams_; + using KafkaStream = memgraph::query::stream::Streams::StreamData<memgraph::query::stream::KafkaStream>; + + auto ReadLock() { return streams_->streams_.ReadLock(); } +}; + +template <typename StorageType> +class StreamsTestFixture : public ::testing::Test { + public: + StreamsTestFixture() { ResetStreamsObject(); } protected: - memgraph::storage::Storage db_; + const std::string testSuite = "query_streams"; std::filesystem::path data_directory_{GetCleanDataDirectory()}; KafkaClusterMock mock_cluster_{std::vector<std::string>{kTopicName}}; // Though there is a Streams object in interpreter context, it makes more sense to use a separate object to test, @@ -62,15 +74,26 @@ class StreamsTest : public ::testing::Test { // Streams constructor. // InterpreterContext::auth_checker_ is used in the Streams object, but only in the message processing part. Because // these tests don't send any messages, the auth_checker_ pointer can be left as nullptr. - memgraph::query::InterpreterContext interpreter_context_{&db_, memgraph::query::InterpreterConfig{}, data_directory_}; + memgraph::query::InterpreterContext interpreter_context_{ + std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), + memgraph::query::InterpreterConfig{}, data_directory_}; std::filesystem::path streams_data_directory_{data_directory_ / "separate-dir-for-test"}; - std::optional<Streams> streams_; + std::optional<StreamsTest> proxyStreams_; - void ResetStreamsObject() { streams_.emplace(&interpreter_context_, streams_data_directory_); } + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + void ResetStreamsObject() { + proxyStreams_.emplace(); + proxyStreams_->streams_.emplace(&interpreter_context_, streams_data_directory_); + } void CheckStreamStatus(const StreamCheckData &check_data) { SCOPED_TRACE(fmt::format("Checking status of '{}'", check_data.name)); - const auto &stream_statuses = streams_->GetStreamInfo(); + const auto &stream_statuses = proxyStreams_->streams_->GetStreamInfo(); auto it = std::find_if(stream_statuses.begin(), stream_statuses.end(), [&check_data](const auto &stream_status) { return stream_status.name == check_data.name; }); ASSERT_NE(it, stream_statuses.end()); @@ -82,10 +105,9 @@ class StreamsTest : public ::testing::Test { } void CheckConfigAndCredentials(const StreamCheckData &check_data) { - const auto locked_streams = streams_->streams_.ReadLock(); + const auto locked_streams = proxyStreams_->ReadLock(); const auto &stream = locked_streams->at(check_data.name); - const auto *stream_data = - std::get_if<memgraph::query::stream::Streams::StreamData<memgraph::query::stream::KafkaStream>>(&stream); + const auto *stream_data = std::get_if<StreamsTest::KafkaStream>(&stream); ASSERT_NE(stream_data, nullptr); const auto stream_info = stream_data->stream_source->ReadLock()->Info(check_data.info.common_info.transformation_name); @@ -94,12 +116,12 @@ class StreamsTest : public ::testing::Test { } void StartStream(StreamCheckData &check_data) { - streams_->Start(check_data.name); + proxyStreams_->streams_->Start(check_data.name); check_data.is_running = true; } void StopStream(StreamCheckData &check_data) { - streams_->Stop(check_data.name); + proxyStreams_->streams_->Stop(check_data.name); check_data.is_running = false; } @@ -124,64 +146,71 @@ class StreamsTest : public ::testing::Test { } }; -TEST_F(StreamsTest, SimpleStreamManagement) { - auto check_data = CreateDefaultStreamCheckData(); - streams_->Create<memgraph::query::stream::KafkaStream>(check_data.name, check_data.info, check_data.owner); - EXPECT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(StreamsTestFixture, StorageTypes); - EXPECT_NO_THROW(streams_->Start(check_data.name)); +TYPED_TEST(StreamsTestFixture, SimpleStreamManagement) { + auto check_data = this->CreateDefaultStreamCheckData(); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(check_data.name, check_data.info, + check_data.owner); + EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); + + EXPECT_NO_THROW(this->proxyStreams_->streams_->Start(check_data.name)); check_data.is_running = true; - EXPECT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); + EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); - EXPECT_NO_THROW(streams_->StopAll()); + EXPECT_NO_THROW(this->proxyStreams_->streams_->StopAll()); check_data.is_running = false; - EXPECT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); + EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); - EXPECT_NO_THROW(streams_->StartAll()); + EXPECT_NO_THROW(this->proxyStreams_->streams_->StartAll()); check_data.is_running = true; - EXPECT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); + EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); - EXPECT_NO_THROW(streams_->Stop(check_data.name)); + EXPECT_NO_THROW(this->proxyStreams_->streams_->Stop(check_data.name)); check_data.is_running = false; - EXPECT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); + EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); - EXPECT_NO_THROW(streams_->Drop(check_data.name)); - EXPECT_TRUE(streams_->GetStreamInfo().empty()); + EXPECT_NO_THROW(this->proxyStreams_->streams_->Drop(check_data.name)); + EXPECT_TRUE(this->proxyStreams_->streams_->GetStreamInfo().empty()); } -TEST_F(StreamsTest, CreateAlreadyExisting) { - auto stream_info = CreateDefaultStreamInfo(); +TYPED_TEST(StreamsTestFixture, CreateAlreadyExisting) { + auto stream_info = this->CreateDefaultStreamInfo(); auto stream_name = GetDefaultStreamName(); - streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, + std::nullopt); try { - streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, + std::nullopt); FAIL() << "Creating already existing stream should throw\n"; } catch (memgraph::query::stream::StreamsException &exception) { EXPECT_EQ(exception.what(), fmt::format("Stream already exists with name '{}'", stream_name)); } } -TEST_F(StreamsTest, DropNotExistingStream) { - const auto stream_info = CreateDefaultStreamInfo(); +TYPED_TEST(StreamsTestFixture, DropNotExistingStream) { + const auto stream_info = this->CreateDefaultStreamInfo(); const auto stream_name = GetDefaultStreamName(); const std::string not_existing_stream_name{"ThisDoesn'tExists"}; - streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, + std::nullopt); try { - streams_->Drop(not_existing_stream_name); + this->proxyStreams_->streams_->Drop(not_existing_stream_name); FAIL() << "Dropping not existing stream should throw\n"; } catch (memgraph::query::stream::StreamsException &exception) { EXPECT_EQ(exception.what(), fmt::format("Couldn't find stream '{}'", not_existing_stream_name)); } } -TEST_F(StreamsTest, RestoreStreams) { +TYPED_TEST(StreamsTestFixture, RestoreStreams) { std::array stream_check_datas{ - CreateDefaultStreamCheckData(), - CreateDefaultStreamCheckData(), - CreateDefaultStreamCheckData(), - CreateDefaultStreamCheckData(), + this->CreateDefaultStreamCheckData(), + this->CreateDefaultStreamCheckData(), + this->CreateDefaultStreamCheckData(), + this->CreateDefaultStreamCheckData(), }; // make the stream infos unique @@ -212,28 +241,29 @@ TEST_F(StreamsTest, RestoreStreams) { } } - mock_cluster_.CreateTopic(stream_info.topics[0]); + this->mock_cluster_.CreateTopic(stream_info.topics[0]); } stream_check_datas[3].owner = {}; const auto check_restore_logic = [&stream_check_datas, this]() { // Reset the Streams object to trigger reloading - ResetStreamsObject(); - EXPECT_TRUE(streams_->GetStreamInfo().empty()); - streams_->RestoreStreams(); - EXPECT_EQ(stream_check_datas.size(), streams_->GetStreamInfo().size()); + this->ResetStreamsObject(); + EXPECT_TRUE(this->proxyStreams_->streams_->GetStreamInfo().empty()); + this->proxyStreams_->streams_->RestoreStreams(); + EXPECT_EQ(stream_check_datas.size(), this->proxyStreams_->streams_->GetStreamInfo().size()); for (const auto &check_data : stream_check_datas) { - ASSERT_NO_FATAL_FAILURE(CheckStreamStatus(check_data)); - ASSERT_NO_FATAL_FAILURE(CheckConfigAndCredentials(check_data)); + ASSERT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data)); + ASSERT_NO_FATAL_FAILURE(this->CheckConfigAndCredentials(check_data)); } }; - streams_->RestoreStreams(); - EXPECT_TRUE(streams_->GetStreamInfo().empty()); + this->proxyStreams_->streams_->RestoreStreams(); + EXPECT_TRUE(this->proxyStreams_->streams_->GetStreamInfo().empty()); for (auto &check_data : stream_check_datas) { - streams_->Create<memgraph::query::stream::KafkaStream>(check_data.name, check_data.info, check_data.owner); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>( + check_data.name, check_data.info, check_data.owner); } { SCOPED_TRACE("After streams are created"); @@ -241,7 +271,7 @@ TEST_F(StreamsTest, RestoreStreams) { } for (auto &check_data : stream_check_datas) { - StartStream(check_data); + this->StartStream(check_data); } { SCOPED_TRACE("After starting streams"); @@ -249,16 +279,16 @@ TEST_F(StreamsTest, RestoreStreams) { } // Stop two of the streams - StopStream(stream_check_datas[1]); - StopStream(stream_check_datas[3]); + this->StopStream(stream_check_datas[1]); + this->StopStream(stream_check_datas[3]); { SCOPED_TRACE("After stopping two streams"); check_restore_logic(); } // Stop the rest of the streams - StopStream(stream_check_datas[0]); - StopStream(stream_check_datas[2]); + this->StopStream(stream_check_datas[0]); + this->StopStream(stream_check_datas[2]); check_restore_logic(); { SCOPED_TRACE("After stopping all streams"); @@ -266,15 +296,16 @@ TEST_F(StreamsTest, RestoreStreams) { } } -TEST_F(StreamsTest, CheckWithTimeout) { - const auto stream_info = CreateDefaultStreamInfo(); +TYPED_TEST(StreamsTestFixture, CheckWithTimeout) { + const auto stream_info = this->CreateDefaultStreamInfo(); const auto stream_name = GetDefaultStreamName(); - streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt); + this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, + std::nullopt); std::chrono::milliseconds timeout{3000}; const auto start = std::chrono::steady_clock::now(); - EXPECT_THROW(streams_->Check(stream_name, timeout, std::nullopt), + EXPECT_THROW(this->proxyStreams_->streams_->Check(stream_name, timeout, std::nullopt), memgraph::integrations::kafka::ConsumerCheckFailedException); const auto end = std::chrono::steady_clock::now(); @@ -283,8 +314,8 @@ TEST_F(StreamsTest, CheckWithTimeout) { EXPECT_LE(elapsed, timeout * 1.2); } -TEST_F(StreamsTest, CheckInvalidConfig) { - auto stream_info = CreateDefaultStreamInfo(); +TYPED_TEST(StreamsTestFixture, CheckInvalidConfig) { + auto stream_info = this->CreateDefaultStreamInfo(); const auto stream_name = GetDefaultStreamName(); static constexpr auto kInvalidConfigName = "doesnt.exist"; static constexpr auto kConfigValue = "myprecious"; @@ -293,12 +324,13 @@ TEST_F(StreamsTest, CheckInvalidConfig) { EXPECT_TRUE(message.find(kInvalidConfigName) != std::string::npos) << message; EXPECT_TRUE(message.find(kConfigValue) != std::string::npos) << message; }; - EXPECT_THROW_WITH_MSG(streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt), + EXPECT_THROW_WITH_MSG(this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>( + stream_name, stream_info, std::nullopt), memgraph::integrations::kafka::SettingCustomConfigFailed, checker); } -TEST_F(StreamsTest, CheckInvalidCredentials) { - auto stream_info = CreateDefaultStreamInfo(); +TYPED_TEST(StreamsTestFixture, CheckInvalidCredentials) { + auto stream_info = this->CreateDefaultStreamInfo(); const auto stream_name = GetDefaultStreamName(); static constexpr auto kInvalidCredentialName = "doesnt.exist"; static constexpr auto kCredentialValue = "myprecious"; @@ -308,6 +340,7 @@ TEST_F(StreamsTest, CheckInvalidCredentials) { EXPECT_TRUE(message.find(memgraph::integrations::kReducted) != std::string::npos) << message; EXPECT_TRUE(message.find(kCredentialValue) == std::string::npos) << message; }; - EXPECT_THROW_WITH_MSG(streams_->Create<memgraph::query::stream::KafkaStream>(stream_name, stream_info, std::nullopt), + EXPECT_THROW_WITH_MSG(this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>( + stream_name, stream_info, std::nullopt), memgraph::integrations::kafka::SettingCustomConfigFailed, checker); } diff --git a/tests/unit/query_trigger.cpp b/tests/unit/query_trigger.cpp index 8a20d1294..af73fe32c 100644 --- a/tests/unit/query_trigger.cpp +++ b/tests/unit/query_trigger.cpp @@ -14,6 +14,7 @@ #include <filesystem> #include <fmt/format.h> +#include "disk_test_utils.hpp" #include "glue/auth_checker.hpp" #include "query/auth_checker.hpp" #include "query/config.hpp" @@ -22,7 +23,9 @@ #include "query/interpreter.hpp" #include "query/trigger.hpp" #include "query/typed_value.hpp" +#include "storage/v2/config.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "utils/exceptions.hpp" #include "utils/memory.hpp" @@ -47,25 +50,35 @@ class MockAuthChecker : public memgraph::query::AuthChecker { }; } // namespace +const std::string testSuite = "query_trigger"; + +template <typename StorageType> class TriggerContextTest : public ::testing::Test { public: - void SetUp() override { db.emplace(); } + void SetUp() override { db = std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)); } void TearDown() override { accessors.clear(); db.reset(); + + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } } - memgraph::storage::Storage::Accessor &StartTransaction() { - accessors.push_back(db->Access()); - return accessors.back(); + memgraph::storage::Storage::Accessor *StartTransaction() { + accessors.emplace_back(db->Access()); + return accessors.back().get(); } protected: - std::optional<memgraph::storage::Storage> db; - std::list<memgraph::storage::Storage::Accessor> accessors; + std::unique_ptr<memgraph::storage::Storage> db; + std::list<std::unique_ptr<memgraph::storage::Storage::Accessor>> accessors; }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(TriggerContextTest, StorageTypes); + namespace { void CheckTypedValueSize(const memgraph::query::TriggerContext &trigger_context, const memgraph::query::TriggerIdentifierTag tag, const size_t expected_size, @@ -101,14 +114,14 @@ void CheckLabelList(const memgraph::query::TriggerContext &trigger_context, // Ensure that TriggerContext returns only valid objects. // Returned TypedValue should always contain only objects // that exist (unless its explicitly created for the deleted object) -TEST_F(TriggerContextTest, ValidObjectsTest) { +TYPED_TEST(TriggerContextTest, ValidObjectsTest) { memgraph::query::TriggerContext trigger_context; memgraph::query::TriggerContextCollector trigger_context_collector{kAllEventTypes}; size_t vertex_count = 0; size_t edge_count = 0; { - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; auto create_vertex = [&] { auto created_vertex = dba.InsertVertex(); @@ -162,7 +175,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { } { - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; trigger_context.AdaptForAccessor(&dba); // Should have one less created object for vertex and edge @@ -175,7 +188,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { size_t deleted_vertex_count = 0; size_t deleted_edge_count = 0; { - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; // register each type of change for each object { @@ -189,6 +202,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { trigger_context_collector.RegisterSetVertexLabel(vertex, dba.NameToLabel("LABEL1")); trigger_context_collector.RegisterRemovedVertexLabel(vertex, dba.NameToLabel("LABEL2")); + dba.PrefetchOutEdges(vertex); auto out_edges = vertex.OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); @@ -259,7 +273,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { // for each update event. // TypedValue of the deleted objects stay the same as they're bound to the transaction which deleted them. { - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; trigger_context.AdaptForAccessor(&dba); auto vertices = dba.Vertices(memgraph::storage::View::OLD); @@ -274,7 +288,7 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { } { - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; trigger_context.AdaptForAccessor(&dba); CheckTypedValueSize(trigger_context, memgraph::query::TriggerIdentifierTag::SET_VERTEX_PROPERTIES, vertex_count, @@ -306,10 +320,10 @@ TEST_F(TriggerContextTest, ValidObjectsTest) { // If the trigger context registered a created object, each future event on the same object will be ignored. // Binding the trigger context to transaction will mean that creating and updating an object in the same transaction // will return only the CREATE event. -TEST_F(TriggerContextTest, ReturnCreateOnlyEvent) { +TYPED_TEST(TriggerContextTest, ReturnCreateOnlyEvent) { memgraph::query::TriggerContextCollector trigger_context_collector{kAllEventTypes}; - memgraph::query::DbAccessor dba{&StartTransaction()}; + memgraph::query::DbAccessor dba{this->StartTransaction()}; auto create_vertex = [&] { auto vertex = dba.InsertVertex(); @@ -370,8 +384,8 @@ void EXPECT_PROP_EQ(const memgraph::query::TypedValue &a, const memgraph::query: // During a transaction, same property for the same object can change multiple times. TriggerContext should ensure // that only the change on the global value is returned (value before the transaction + latest value after the // transaction) everything inbetween should be ignored. -TEST_F(TriggerContextTest, GlobalPropertyChange) { - memgraph::query::DbAccessor dba{&StartTransaction()}; +TYPED_TEST(TriggerContextTest, GlobalPropertyChange) { + memgraph::query::DbAccessor dba{this->StartTransaction()}; const std::unordered_set<memgraph::query::TriggerEventType> event_types{ memgraph::query::TriggerEventType::VERTEX_UPDATE}; @@ -565,8 +579,8 @@ TEST_F(TriggerContextTest, GlobalPropertyChange) { } // Same as above, but for label changes -TEST_F(TriggerContextTest, GlobalLabelChange) { - memgraph::query::DbAccessor dba{&StartTransaction()}; +TYPED_TEST(TriggerContextTest, GlobalLabelChange) { + memgraph::query::DbAccessor dba{this->StartTransaction()}; const std::unordered_set<memgraph::query::TriggerEventType> event_types{ memgraph::query::TriggerEventType::VERTEX_UPDATE}; @@ -796,87 +810,88 @@ void CheckFilters(const std::unordered_set<memgraph::query::TriggerEventType> &e } } // namespace -TEST_F(TriggerContextTest, Filtering) { +TYPED_TEST(TriggerContextTest, Filtering) { using TET = memgraph::query::TriggerEventType; // Check all event type individually { SCOPED_TRACE("TET::ANY"); CheckFilters({TET::ANY}, ShouldRegisterExpectation{true, true, true}, ShouldRegisterExpectation{true, true, true}, - &StartTransaction()); + this->StartTransaction()); } { SCOPED_TRACE("TET::VERTEX_CREATE"); CheckFilters({TET::VERTEX_CREATE}, ShouldRegisterExpectation{true, false, false}, - ShouldRegisterExpectation{false, false, false}, &StartTransaction()); + ShouldRegisterExpectation{false, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::EDGE_CREATE"); CheckFilters({TET::EDGE_CREATE}, ShouldRegisterExpectation{false, false, false}, - ShouldRegisterExpectation{true, false, false}, &StartTransaction()); + ShouldRegisterExpectation{true, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::CREATE"); CheckFilters({TET::CREATE}, ShouldRegisterExpectation{true, false, false}, - ShouldRegisterExpectation{true, false, false}, &StartTransaction()); + ShouldRegisterExpectation{true, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::VERTEX_DELETE"); CheckFilters({TET::VERTEX_DELETE}, ShouldRegisterExpectation{true, true, false}, - ShouldRegisterExpectation{false, false, false}, &StartTransaction()); + ShouldRegisterExpectation{false, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::EDGE_DELETE"); CheckFilters({TET::EDGE_DELETE}, ShouldRegisterExpectation{false, false, false}, - ShouldRegisterExpectation{true, true, false}, &StartTransaction()); + ShouldRegisterExpectation{true, true, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::DELETE"); CheckFilters({TET::DELETE}, ShouldRegisterExpectation{true, true, false}, - ShouldRegisterExpectation{true, true, false}, &StartTransaction()); + ShouldRegisterExpectation{true, true, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::VERTEX_UPDATE"); CheckFilters({TET::VERTEX_UPDATE}, ShouldRegisterExpectation{true, false, true}, - ShouldRegisterExpectation{false, false, false}, &StartTransaction()); + ShouldRegisterExpectation{false, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::EDGE_UPDATE"); CheckFilters({TET::EDGE_UPDATE}, ShouldRegisterExpectation{false, false, false}, - ShouldRegisterExpectation{true, false, true}, &StartTransaction()); + ShouldRegisterExpectation{true, false, true}, this->StartTransaction()); } { SCOPED_TRACE("TET::UPDATE"); CheckFilters({TET::UPDATE}, ShouldRegisterExpectation{true, false, true}, - ShouldRegisterExpectation{true, false, true}, &StartTransaction()); + ShouldRegisterExpectation{true, false, true}, this->StartTransaction()); } // Some combined versions { SCOPED_TRACE("TET::VERTEX_UPDATE, TET::EDGE_UPDATE"); CheckFilters({TET::VERTEX_UPDATE, TET::EDGE_UPDATE}, ShouldRegisterExpectation{true, false, true}, - ShouldRegisterExpectation{true, false, true}, &StartTransaction()); + ShouldRegisterExpectation{true, false, true}, this->StartTransaction()); } { SCOPED_TRACE("TET::VERTEX_UPDATE, TET::EDGE_UPDATE, TET::DELETE"); CheckFilters({TET::VERTEX_UPDATE, TET::EDGE_UPDATE, TET::DELETE}, ShouldRegisterExpectation{true, true, true}, - ShouldRegisterExpectation{true, true, true}, &StartTransaction()); + ShouldRegisterExpectation{true, true, true}, this->StartTransaction()); } { SCOPED_TRACE("TET::UPDATE, TET::VERTEX_DELETE, TET::EDGE_DELETE"); CheckFilters({TET::UPDATE, TET::VERTEX_DELETE, TET::EDGE_DELETE}, ShouldRegisterExpectation{true, true, true}, - ShouldRegisterExpectation{true, true, true}, &StartTransaction()); + ShouldRegisterExpectation{true, true, true}, this->StartTransaction()); } { SCOPED_TRACE("TET::VERTEX_CREATE, TET::VERTEX_UPDATE"); CheckFilters({TET::VERTEX_CREATE, TET::VERTEX_UPDATE}, ShouldRegisterExpectation{true, false, true}, - ShouldRegisterExpectation{false, false, false}, &StartTransaction()); + ShouldRegisterExpectation{false, false, false}, this->StartTransaction()); } { SCOPED_TRACE("TET::EDGE_CREATE, TET::EDGE_UPDATE"); CheckFilters({TET::EDGE_CREATE, TET::EDGE_UPDATE}, ShouldRegisterExpectation{false, false, false}, - ShouldRegisterExpectation{true, false, true}, &StartTransaction()); + ShouldRegisterExpectation{true, false, true}, this->StartTransaction()); } } +template <typename StorageType> class TriggerStoreTest : public ::testing::Test { protected: const std::filesystem::path testing_directory{std::filesystem::temp_directory_path() / "MG_test_unit_query_trigger"}; @@ -884,8 +899,10 @@ class TriggerStoreTest : public ::testing::Test { void SetUp() override { Clear(); - storage_accessor.emplace(storage.Access()); - dba.emplace(&*storage_accessor); + config = disk_test_utils::GenerateOnDiskConfig(testSuite); + storage = std::make_unique<StorageType>(config); + storage_accessor = storage->Access(); + dba.emplace(storage_accessor.get()); } void TearDown() override { @@ -893,6 +910,11 @@ class TriggerStoreTest : public ::testing::Test { dba.reset(); storage_accessor.reset(); + storage.reset(); + + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } } std::optional<memgraph::query::DbAccessor> dba; @@ -906,16 +928,20 @@ class TriggerStoreTest : public ::testing::Test { std::filesystem::remove_all(testing_directory); } - memgraph::storage::Storage storage; - std::optional<memgraph::storage::Storage::Accessor> storage_accessor; + memgraph::storage::Config config; + std::unique_ptr<memgraph::storage::Storage> storage; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_accessor; }; -TEST_F(TriggerStoreTest, Restore) { +TYPED_TEST_CASE(TriggerStoreTest, StorageTypes); + +TYPED_TEST(TriggerStoreTest, Restore) { std::optional<memgraph::query::TriggerStore> store; const auto reset_store = [&] { - store.emplace(testing_directory); - store->RestoreTriggers(&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, &auth_checker); + store.emplace(this->testing_directory); + store->RestoreTriggers(&this->ast_cache, &*this->dba, memgraph::query::InterpreterConfig::Query{}, + &this->auth_checker); }; reset_store(); @@ -936,13 +962,13 @@ TEST_F(TriggerStoreTest, Restore) { store->AddTrigger( trigger_name_before, trigger_statement, std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{1}}}, - event_type, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker); + event_type, memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker); store->AddTrigger( trigger_name_after, trigger_statement, std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{"value"}}}, - event_type, memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, {owner}, &auth_checker); + event_type, memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, {owner}, &this->auth_checker); const auto check_triggers = [&] { ASSERT_EQ(store->GetTriggerInfo().size(), 2); @@ -987,38 +1013,38 @@ TEST_F(TriggerStoreTest, Restore) { check_empty(); } -TEST_F(TriggerStoreTest, AddTrigger) { - memgraph::query::TriggerStore store{testing_directory}; +TYPED_TEST(TriggerStoreTest, AddTrigger) { + memgraph::query::TriggerStore store{this->testing_directory}; // Invalid query in statements ASSERT_THROW(store.AddTrigger("trigger", "RETUR 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker), + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker), memgraph::utils::BasicException); ASSERT_THROW(store.AddTrigger("trigger", "RETURN createdEdges", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker), + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker), memgraph::utils::BasicException); ASSERT_THROW(store.AddTrigger("trigger", "RETURN $parameter", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker), + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker), memgraph::utils::BasicException); ASSERT_NO_THROW(store.AddTrigger( "trigger", "RETURN $parameter", std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{1}}}, - memgraph::query::TriggerEventType::VERTEX_CREATE, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker)); + memgraph::query::TriggerEventType::VERTEX_CREATE, memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, + &*this->dba, memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker)); // Inserting with the same name ASSERT_THROW(store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker), + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker), memgraph::utils::BasicException); ASSERT_THROW(store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker), + memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker), memgraph::utils::BasicException); ASSERT_EQ(store.GetTriggerInfo().size(), 1); @@ -1026,28 +1052,28 @@ TEST_F(TriggerStoreTest, AddTrigger) { ASSERT_EQ(store.AfterCommitTriggers().size(), 0); } -TEST_F(TriggerStoreTest, DropTrigger) { - memgraph::query::TriggerStore store{testing_directory}; +TYPED_TEST(TriggerStoreTest, DropTrigger) { + memgraph::query::TriggerStore store{this->testing_directory}; ASSERT_THROW(store.DropTrigger("Unknown"), memgraph::utils::BasicException); const auto *trigger_name = "trigger"; store.AddTrigger(trigger_name, "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker); + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker); ASSERT_THROW(store.DropTrigger("Unknown"), memgraph::utils::BasicException); ASSERT_NO_THROW(store.DropTrigger(trigger_name)); ASSERT_EQ(store.GetTriggerInfo().size(), 0); } -TEST_F(TriggerStoreTest, TriggerInfo) { - memgraph::query::TriggerStore store{testing_directory}; +TYPED_TEST(TriggerStoreTest, TriggerInfo) { + memgraph::query::TriggerStore store{this->testing_directory}; std::vector<memgraph::query::TriggerStore::TriggerInfo> expected_info; store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker); + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker); expected_info.push_back({"trigger", "RETURN 1", memgraph::query::TriggerEventType::VERTEX_CREATE, memgraph::query::TriggerPhase::BEFORE_COMMIT}); @@ -1066,8 +1092,8 @@ TEST_F(TriggerStoreTest, TriggerInfo) { check_trigger_info(); store.AddTrigger("edge_update_trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::EDGE_UPDATE, - memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker); + memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker); expected_info.push_back({"edge_update_trigger", "RETURN 1", memgraph::query::TriggerEventType::EDGE_UPDATE, memgraph::query::TriggerPhase::AFTER_COMMIT}); @@ -1088,8 +1114,8 @@ TEST_F(TriggerStoreTest, TriggerInfo) { check_trigger_info(); } -TEST_F(TriggerStoreTest, AnyTriggerAllKeywords) { - memgraph::query::TriggerStore store{testing_directory}; +TYPED_TEST(TriggerStoreTest, AnyTriggerAllKeywords) { + memgraph::query::TriggerStore store{this->testing_directory}; using namespace std::literals; @@ -1180,19 +1206,19 @@ TEST_F(TriggerStoreTest, AnyTriggerAllKeywords) { for (const auto keyword : keywords) { SCOPED_TRACE(keyword); EXPECT_NO_THROW(store.AddTrigger(trigger_name, fmt::format("RETURN {}", keyword), {}, event_type, - memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, - memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker)); + memgraph::query::TriggerPhase::BEFORE_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &this->auth_checker)); store.DropTrigger(trigger_name); } } } -TEST_F(TriggerStoreTest, AuthCheckerUsage) { +TYPED_TEST(TriggerStoreTest, AuthCheckerUsage) { using Privilege = memgraph::query::AuthQuery::Privilege; using ::testing::_; using ::testing::ElementsAre; using ::testing::Return; - std::optional<memgraph::query::TriggerStore> store{testing_directory}; + std::optional<memgraph::query::TriggerStore> store{this->testing_directory}; const std::optional<std::string> owner{"testing_owner"}; MockAuthChecker mock_checker; @@ -1205,32 +1231,32 @@ TEST_F(TriggerStoreTest, AuthCheckerUsage) { ASSERT_NO_THROW(store->AddTrigger("successfull_trigger_1", "CREATE (n:VERTEX) RETURN n", {}, memgraph::query::TriggerEventType::EDGE_UPDATE, - memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, + memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker)); ASSERT_NO_THROW(store->AddTrigger("successfull_trigger_2", "CREATE (n:VERTEX) RETURN n", {}, memgraph::query::TriggerEventType::EDGE_UPDATE, - memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, + memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, memgraph::query::InterpreterConfig::Query{}, owner, &mock_checker)); EXPECT_CALL(mock_checker, IsUserAuthorized(std::optional<std::string>{}, ElementsAre(Privilege::MATCH))) .Times(1) .WillOnce(Return(false)); - ASSERT_THROW( - store->AddTrigger("unprivileged_trigger", "MATCH (n:VERTEX) RETURN n", {}, - memgraph::query::TriggerEventType::EDGE_UPDATE, memgraph::query::TriggerPhase::AFTER_COMMIT, - &ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker); - , memgraph::utils::BasicException); + ASSERT_THROW(store->AddTrigger("unprivileged_trigger", "MATCH (n:VERTEX) RETURN n", {}, + memgraph::query::TriggerEventType::EDGE_UPDATE, + memgraph::query::TriggerPhase::AFTER_COMMIT, &this->ast_cache, &*this->dba, + memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker); + , memgraph::utils::BasicException); - store.emplace(testing_directory); + store.emplace(this->testing_directory); EXPECT_CALL(mock_checker, IsUserAuthorized(std::optional<std::string>{}, ElementsAre(Privilege::CREATE))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(mock_checker, IsUserAuthorized(owner, ElementsAre(Privilege::CREATE))).Times(1).WillOnce(Return(true)); - ASSERT_NO_THROW( - store->RestoreTriggers(&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, &mock_checker)); + ASSERT_NO_THROW(store->RestoreTriggers(&this->ast_cache, &*this->dba, memgraph::query::InterpreterConfig::Query{}, + &mock_checker)); const auto triggers = store->GetTriggerInfo(); ASSERT_EQ(triggers.size(), 1); diff --git a/tests/unit/query_variable_start_planner.cpp b/tests/unit/query_variable_start_planner.cpp index 00f794ffc..615f10b87 100644 --- a/tests/unit/query_variable_start_planner.cpp +++ b/tests/unit/query_variable_start_planner.cpp @@ -12,11 +12,14 @@ #include <algorithm> #include <variant> +#include "disk_test_utils.hpp" #include "gtest/gtest.h" #include "query/frontend/semantic/symbol_generator.hpp" #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/planner.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "utils/algorithm.hpp" #include "query_plan_common.hpp" @@ -86,29 +89,44 @@ void CheckPlansProduce(size_t expected_plan_count, memgraph::query::CypherQuery } } -TEST(TestVariableStartPlanner, MatchReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +template <typename StorageType> +class TestVariableStartPlanner : public testing::Test { + public: + const std::string testSuite = "query_variable_start_planner"; + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; + AstStorage storage; + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(TestVariableStartPlanner, StorageTypes); + +TYPED_TEST(TestVariableStartPlanner, MatchReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); ASSERT_TRUE(dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("r")).HasValue()); dba.AdvanceCommand(); // Test MATCH (n) -[r]-> (m) RETURN n - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))), RETURN("n"))); // We have 2 nodes `n` and `m` from which we could start, so expect 2 plans. - CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(2, query, this->storage, &dba, [&](const auto &results) { // We expect to produce only a single (v1) node. AssertRows(results, {{TypedValue(memgraph::query::VertexAccessor(v1))}}, dba); }); } -TEST(TestVariableStartPlanner, MatchTripletPatternReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchTripletPatternReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3) auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -118,32 +136,29 @@ TEST(TestVariableStartPlanner, MatchTripletPatternReturn) { dba.AdvanceCommand(); { // Test `MATCH (n) -[r]-> (m) -[e]-> (l) RETURN n` - AstStorage storage; auto *query = QUERY(SINGLE_QUERY( MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"), EDGE("e", Direction::OUT), NODE("l"))), RETURN("n"))); // We have 3 nodes: `n`, `m` and `l` from which we could start. - CheckPlansProduce(3, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(3, query, this->storage, &dba, [&](const auto &results) { // We expect to produce only a single (v1) node. AssertRows(results, {{TypedValue(memgraph::query::VertexAccessor(v1))}}, dba); }); } { // Equivalent to `MATCH (n) -[r]-> (m), (m) -[e]-> (l) RETURN n`. - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m")), PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))), RETURN("n"))); - CheckPlansProduce(3, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(3, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{TypedValue(memgraph::query::VertexAccessor(v1))}}, dba); }); } } -TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3) auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -152,13 +167,12 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { ASSERT_TRUE(dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("r")).HasValue()); dba.AdvanceCommand(); // Test MATCH (n) -[r]-> (m) OPTIONAL MATCH (m) -[e]-> (l) RETURN n, l - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))), OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))), RETURN("n", "l"))); // We have 2 nodes `n` and `m` from which we could start the MATCH, and 2 // nodes for OPTIONAL MATCH. This should produce 2 * 2 plans. - CheckPlansProduce(4, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(4, query, this->storage, &dba, [&](const auto &results) { // We expect to produce 2 rows: // * (v1), (v3) // * (v2), null @@ -169,10 +183,9 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { }); } -TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r]-> (v2) memgraph::query::VertexAccessor v1(dba.InsertVertex()); memgraph::query::VertexAccessor v2(dba.InsertVertex()); @@ -182,45 +195,41 @@ TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) { dba.AdvanceCommand(); // Test MATCH (n) -[r]-> (m) OPTIONAL MATCH (m) -[e]-> (l) // MERGE (u) -[q:r]-> (v) RETURN n, m, l, u, v - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))), OPTIONAL_MATCH(PATTERN(NODE("m"), EDGE("e", Direction::OUT), NODE("l"))), MERGE(PATTERN(NODE("u"), EDGE("q", Direction::OUT, {r_type_name}), NODE("v"))), RETURN("n", "m", "l", "u", "v"))); // Since MATCH, OPTIONAL MATCH and MERGE each have 2 nodes from which we can // start, we generate 2 * 2 * 2 plans. - CheckPlansProduce(8, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(8, query, this->storage, &dba, [&](const auto &results) { // We expect to produce a single row: (v1), (v2), null, (v1), (v2) AssertRows(results, {{TypedValue(v1), TypedValue(v2), TypedValue(), TypedValue(v1), TypedValue(v2)}}, dba); }); } -TEST(TestVariableStartPlanner, MatchWithMatchReturn) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchWithMatchReturn) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r]-> (v2) memgraph::query::VertexAccessor v1(dba.InsertVertex()); memgraph::query::VertexAccessor v2(dba.InsertVertex()); ASSERT_TRUE(dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("r")).HasValue()); dba.AdvanceCommand(); // Test MATCH (n) -[r]-> (m) WITH n MATCH (m) -[r]-> (l) RETURN n, m, l - AstStorage storage; auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), EDGE("r", Direction::OUT), NODE("m"))), WITH("n"), MATCH(PATTERN(NODE("m"), EDGE("r", Direction::OUT), NODE("l"))), RETURN("n", "m", "l"))); // We can start from 2 nodes in each match. Since WITH separates query parts, // we expect to get 2 plans for each, which totals 2 * 2. - CheckPlansProduce(4, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(4, query, this->storage, &dba, [&](const auto &results) { // We expect to produce a single row: (v1), (v1), (v2) AssertRows(results, {{TypedValue(v1), TypedValue(v1), TypedValue(v2)}}, dba); }); } -TEST(TestVariableStartPlanner, MatchVariableExpand) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchVariableExpand) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r1]-> (v2) -[:r2]-> (v3) auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -229,7 +238,6 @@ TEST(TestVariableStartPlanner, MatchVariableExpand) { auto r2 = *dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("r2")); dba.AdvanceCommand(); // Test MATCH (n) -[r*]-> (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::OUT); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); // We expect to get a single column with the following rows: @@ -237,15 +245,14 @@ TEST(TestVariableStartPlanner, MatchVariableExpand) { TypedValue r2_list(std::vector<TypedValue>{TypedValue(r2)}); // [r2] // [r1, r2] TypedValue r1_r2_list(std::vector<TypedValue>{TypedValue(r1), TypedValue(r2)}); - CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(2, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{r1_list}, {r2_list}, {r1_r2_list}}, dba); }); } -TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3}) auto v1 = dba.InsertVertex(); @@ -258,24 +265,22 @@ TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) { auto r2 = *dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("r2")); dba.AdvanceCommand(); // Test MATCH (n) -[r*..n.id]-> (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::OUT); - edge->upper_bound_ = PROPERTY_LOOKUP("n", id); + edge->upper_bound_ = PROPERTY_LOOKUP(dba, "n", id); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), edge, NODE("m"))), RETURN("r"))); // We expect to get a single column with the following rows: // [r1] (v1 -[*..1]-> v2) TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)}); // [r2] (v2 -[*..2]-> v3) TypedValue r2_list(std::vector<TypedValue>{TypedValue(r2)}); - CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(2, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{r1_list}, {r2_list}}, dba); }); } -TEST(TestVariableStartPlanner, MatchVariableExpandBoth) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandBoth) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2) -[:r2]-> (v3) auto v1 = dba.InsertVertex(); @@ -286,24 +291,22 @@ TEST(TestVariableStartPlanner, MatchVariableExpandBoth) { auto r2 = *dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("r2")); dba.AdvanceCommand(); // Test MATCH (n {id:1}) -[r*]- (m) RETURN r - AstStorage storage; auto edge = EDGE_VARIABLE("r", Type::DEPTH_FIRST, Direction::BOTH); auto node_n = NODE("n"); - std::get<0>(node_n->properties_)[storage.GetPropertyIx("id")] = LITERAL(1); + std::get<0>(node_n->properties_)[this->storage.GetPropertyIx("id")] = LITERAL(1); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node_n, edge, NODE("m"))), RETURN("r"))); // We expect to get a single column with the following rows: TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)}); // [r1] // [r1, r2] TypedValue r1_r2_list(std::vector<TypedValue>{TypedValue(r1), TypedValue(r2)}); - CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(2, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{r1_list}, {r1_r2_list}}, dba); }); } -TEST(TestVariableStartPlanner, MatchBfs) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); +TYPED_TEST(TestVariableStartPlanner, MatchBfs) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3}) auto v1 = dba.InsertVertex(); @@ -316,24 +319,21 @@ TEST(TestVariableStartPlanner, MatchBfs) { ASSERT_TRUE(dba.InsertEdge(&v2, &v3, dba.NameToEdgeType("r2")).HasValue()); dba.AdvanceCommand(); // Test MATCH (n) -[r *bfs..10](r, n | n.id <> 3)]-> (m) RETURN r - AstStorage storage; - auto *bfs = storage.Create<memgraph::query::EdgeAtom>(IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, Direction::OUT, - std::vector<memgraph::query::EdgeTypeIx>{}); + auto *bfs = this->storage.template Create<memgraph::query::EdgeAtom>( + IDENT("r"), EdgeAtom::Type::BREADTH_FIRST, Direction::OUT, std::vector<memgraph::query::EdgeTypeIx>{}); bfs->filter_lambda_.inner_edge = IDENT("r"); bfs->filter_lambda_.inner_node = IDENT("n"); - bfs->filter_lambda_.expression = NEQ(PROPERTY_LOOKUP("n", id), LITERAL(3)); + bfs->filter_lambda_.expression = NEQ(PROPERTY_LOOKUP(dba, "n", id), LITERAL(3)); bfs->upper_bound_ = LITERAL(10); auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"), bfs, NODE("m"))), RETURN("r"))); // We expect to get a single column with the following rows: TypedValue r1_list(std::vector<TypedValue>{TypedValue(r1)}); // [r1] - CheckPlansProduce(2, query, storage, &dba, [&](const auto &results) { AssertRows(results, {{r1_list}}, dba); }); + CheckPlansProduce(2, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{r1_list}}, dba); }); } -TEST(TestVariableStartPlanner, TestBasicSubquery) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(TestVariableStartPlanner, TestBasicSubquery) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -343,7 +343,7 @@ TEST(TestVariableStartPlanner, TestBasicSubquery) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), CALL_SUBQUERY(subquery), RETURN("n", "m"))); - CheckPlansProduce(1, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(1, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{TypedValue(v1), TypedValue(v1)}, {TypedValue(v1), TypedValue(v2)}, @@ -353,11 +353,9 @@ TEST(TestVariableStartPlanner, TestBasicSubquery) { }); } -TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -371,16 +369,14 @@ TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m1"), EDGE("r1", EdgeAtom::Direction::OUT), NODE("n1"))), CALL_SUBQUERY(subquery), RETURN("m1", "m2"))); - CheckPlansProduce(4, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(4, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{TypedValue(v1), TypedValue(v1)}}, dba); }); } -TEST(TestVariableStartPlanner, TestSubqueryWithUnion) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithUnion) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); auto v1 = dba.InsertVertex(); @@ -401,16 +397,14 @@ TEST(TestVariableStartPlanner, TestSubqueryWithUnion) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m1"), EDGE("r1", EdgeAtom::Direction::OUT), NODE("n1"))), CALL_SUBQUERY(subquery), RETURN("m1", "n2"))); - CheckPlansProduce(8, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(8, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{TypedValue(v1), TypedValue(v2)}, {TypedValue(v1), TypedValue(v2)}}, dba); }); } -TEST(TestVariableStartPlanner, TestSubqueryWithTripleUnion) { - memgraph::storage::Storage db; - auto storage_dba = db.Access(); - memgraph::query::DbAccessor dba(&storage_dba); - AstStorage storage; +TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithTripleUnion) { + auto storage_dba = this->db->Access(); + memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); auto v1 = dba.InsertVertex(); @@ -433,7 +427,7 @@ TEST(TestVariableStartPlanner, TestSubqueryWithTripleUnion) { auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("m1"), EDGE("r1", EdgeAtom::Direction::OUT), NODE("n1"))), CALL_SUBQUERY(subquery), RETURN("m1", "n2"))); - CheckPlansProduce(16, query, storage, &dba, [&](const auto &results) { + CheckPlansProduce(16, query, this->storage, &dba, [&](const auto &results) { AssertRows(results, {{TypedValue(v1), TypedValue(v2)}, {TypedValue(v1), TypedValue(v2)}, {TypedValue(v1), TypedValue(v2)}}, dba); diff --git a/tests/unit/storage_rocks.cpp b/tests/unit/storage_rocks.cpp new file mode 100644 index 000000000..30320a529 --- /dev/null +++ b/tests/unit/storage_rocks.cpp @@ -0,0 +1,119 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +// #include <gmock/gmock.h> +// #include <gtest/gtest.h> +// #include <cassert> +// #include <exception> +// #include <string> +// #include <unordered_set> + +// #include "query/common.hpp" +// #include "query/db_accessor.hpp" +// #include "storage/v2/delta.hpp" +// #include "storage/v2/disk/storage.hpp" +// #include "storage/v2/id_types.hpp" +// #include "storage/v2/isolation_level.hpp" +// #include "storage/v2/property_value.hpp" +// #include "storage/v2/storage.hpp" +// #include "storage/v2/vertex_accessor.hpp" +// #include "storage/v2/view.hpp" +// #include "utils/rocksdb_serialization.hpp" + +// class RocksDBStorageTest : public ::testing::TestWithParam<bool> { +// public: +// RocksDBStorageTest() { storage = std::unique_ptr<memgraph::storage::Storage>(new memgraph::storage::DiskStorage()); +// } + +// ~RocksDBStorageTest() override {} + +// protected: +// std::unique_ptr<memgraph::storage::Storage> storage; +// }; + +// /// Serialize vertex which contains only GID. +// TEST_F(RocksDBStorageTest, SerializeVertexGID) { +// auto acc = storage->Access(); +// auto vertex = acc->CreateVertex(); +// auto gid = vertex.Gid(); +// ASSERT_EQ(memgraph::utils::SerializeVertex(*vertex.vertex_), "|" + memgraph::utils::SerializeIdType(gid)); +// } + +/// Serialize vertex with gid and its single label. +// TEST_F(RocksDBStorageTest, SerializeVertexGIDLabels) { +// auto acc = storage->Access(); +// auto vertex = acc->CreateVertex(); +// auto ser_player_label = acc->NameToLabel("Player"); +// auto player_result = vertex.AddLabel(ser_player_label); +// auto gid = vertex.Gid(); +// ASSERT_EQ(memgraph::utils::SerializeVertex(*vertex.vertex_), +// std::to_string(ser_player_label.AsInt()) + "|" + memgraph::utils::SerializeIdType(gid)); +// } + +// /// Serialize vertex with gid and its multiple labels. +// TEST_F(RocksDBStorageTest, SerializeVertexGIDMultipleLabels) { +// auto acc = storage->Access(); +// auto vertex = acc->CreateVertex(); +// auto ser_player_label = acc->NameToLabel("Player"); +// auto ser_person_label = acc->NameToLabel("Person"); +// auto ser_ball_label = acc->NameToLabel("Ball"); +// // NOLINTNEXTLINE +// auto player_res = vertex.AddLabel(ser_player_label); +// auto person_res = vertex.AddLabel(ser_person_label); +// auto ball_res = vertex.AddLabel(ser_ball_label); +// auto gid = vertex.Gid(); +// ASSERT_EQ(memgraph::utils::SerializeVertex(*vertex.vertex_), +// std::to_string(ser_player_label.AsInt()) + "," + std::to_string(ser_person_label.AsInt()) + "," + +// std::to_string(ser_ball_label.AsInt()) + "|" + memgraph::utils::SerializeIdType(gid)); +// } + +// /// Serialize edge. +// TEST_F(RocksDBStorageTest, SerializeEdge) { +// auto acc = storage->Access(); +// auto vertex1 = acc->CreateVertex(); +// auto vertex2 = acc->CreateVertex(); +// auto edge = acc->CreateEdge(&vertex1, &vertex2, acc->NameToEdgeType("KNOWS")); +// auto gid = edge->Gid(); +// auto ser_result = memgraph::utils::SerializeEdge(vertex1.Gid(), vertex2.Gid(), edge->EdgeType(), edge->edge_.ptr); +// ASSERT_EQ(ser_result.first, +// memgraph::utils::SerializeIdType(vertex1.Gid()) + "|" + memgraph::utils::SerializeIdType(vertex2.Gid()) + +// "|0|" + std::to_string(edge->EdgeType().AsInt()) + "|" + memgraph::utils::SerializeIdType(gid)); +// ASSERT_EQ(ser_result.second, +// memgraph::utils::SerializeIdType(vertex2.Gid()) + "|" + memgraph::utils::SerializeIdType(vertex1.Gid()) + +// "|1|" + std::to_string(edge->EdgeType().AsInt()) + "|" + memgraph::utils::SerializeIdType(gid)); +// } + +// TEST_F(RocksDBStorageTest, DeserializeVertex) { +// NOTE: This test would fail in the case of snaphsot isolation because of the way in which RocksDB +// serializes commit timestamp. +// const char *serialized_vertex = "1|1"; +// auto acc = storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); +// auto *acc_ptr = static_cast<memgraph::storage::DiskStorage::DiskAccessor *>(acc.get()); +// const char *value = "garbage"; +// auto vertex = acc_ptr->DeserializeVertex(serialized_vertex, value); +// ASSERT_EQ(vertex->Gid().AsInt(), 1); +// ASSERT_EQ(*vertex->HasLabel(memgraph::storage::LabelId::FromUint(1), memgraph::storage::View::OLD), true); +// } + +// TEST_F(RocksDBStorageTest, DeserializeEdge) { +// NOTE: This test would fail in the case of snaphsot isolation because of the way in which RocksDB +// serializes commit timestamp. +// auto acc = storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); +// auto vertex1 = acc->CreateVertex(); +// auto vertex2 = acc->CreateVertex(); +// auto serialized_edge = fmt::format("{}|{}|0|1|2", vertex1.Gid().AsInt(), vertex2.Gid().AsInt()); +// auto acc_ptr = static_cast<memgraph::storage::DiskStorage::DiskAccessor *>(acc.get()); +// auto edge = acc_ptr->DeserializeEdge(serialized_edge, "garbage"); +// ASSERT_EQ(edge->Gid().AsInt(), 2); +// ASSERT_EQ(edge->EdgeType().AsInt(), 1); +// ASSERT_EQ(edge->from_vertex_->gid.AsInt(), vertex1.Gid().AsInt()); +// ASSERT_EQ(edge->to_vertex_->gid.AsInt(), vertex2.Gid().AsInt()); +// } diff --git a/tests/unit/storage_test_utils.cpp b/tests/unit/storage_test_utils.cpp index 8efcfa0b5..2620666d0 100644 --- a/tests/unit/storage_test_utils.cpp +++ b/tests/unit/storage_test_utils.cpp @@ -18,3 +18,14 @@ size_t CountVertices(memgraph::storage::Storage::Accessor &storage_accessor, mem ; return count; } + +std::string_view StorageModeToString(memgraph::storage::StorageMode storage_mode) { + switch (storage_mode) { + case memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL: + return "IN_MEMORY_ANALYTICAL"; + case memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL: + return "IN_MEMORY_TRANSACTIONAL"; + case memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL: + return "ON_DISK_TRANSACTIONAL"; + } +} diff --git a/tests/unit/storage_v2.cpp b/tests/unit/storage_v2.cpp index b268ca8f8..258fe6d37 100644 --- a/tests/unit/storage_v2.cpp +++ b/tests/unit/storage_v2.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,593 +12,629 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <filesystem> #include <limits> +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/vertex_accessor.hpp" #include "storage_test_utils.hpp" +using testing::Types; using testing::UnorderedElementsAre; +template <typename StorageType> +class StorageV2Test : public testing::Test { + public: + StorageV2Test() { + config_ = disk_test_utils::GenerateOnDiskConfig(testSuite); + store = std::make_unique<StorageType>(config_); + } + + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + store.reset(nullptr); + } + + const std::string testSuite = "storage_v2"; + std::unique_ptr<memgraph::storage::Storage> store; + memgraph::storage::Config config_; +}; + +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(StorageV2Test, StorageTypes); + // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, Commit) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, Commit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::NEW); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto res = acc.DeleteVertex(&*vertex); + auto res = acc->DeleteVertex(&*vertex); ASSERT_FALSE(res.HasError()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); - acc.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); + acc->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, Abort) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, Abort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - acc.Abort(); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + acc->Abort(); } { - auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, AdvanceCommandCommit) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, AdvanceCommandCommit) { memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); + auto acc = this->store->Access(); - auto vertex1 = acc.CreateVertex(); + auto vertex1 = acc->CreateVertex(); gid1 = vertex1.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - auto vertex2 = acc.CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid2 = vertex2.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 2U); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 2U); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - ASSERT_TRUE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 2U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 2U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 2U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 2U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, AdvanceCommandAbort) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, AdvanceCommandAbort) { memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); + auto acc = this->store->Access(); - auto vertex1 = acc.CreateVertex(); + auto vertex1 = acc->CreateVertex(); gid1 = vertex1.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - auto vertex2 = acc.CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid2 = vertex2.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 2U); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 2U); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, SnapshotIsolation) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, SnapshotIsolation) { + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto acc1 = store.Access(); - auto acc2 = store.Access(); - - auto vertex = acc1.CreateVertex(); + auto vertex = acc1->CreateVertex(); auto gid = vertex.Gid(); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 1U); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc1.Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 0U); - acc2.Abort(); + acc2->Abort(); - auto acc3 = store.Access(); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); - acc3.Abort(); + auto acc3 = this->store->Access(); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); + acc3->Abort(); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, AccessorMove) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, AccessorMove) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); - memgraph::storage::Storage::Accessor moved(std::move(acc)); + auto moved(std::move(acc)); - ASSERT_FALSE(moved.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(moved, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(moved.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(moved, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(moved->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*moved, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(moved->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*moved, memgraph::storage::View::NEW), 1U); - ASSERT_FALSE(moved.Commit().HasError()); + ASSERT_FALSE(moved->Commit().HasError()); } { - auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteCommit) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); - - auto acc1 = store.Access(); // read transaction - auto acc2 = store.Access(); // write transaction + auto acc1 = this->store->Access(); // read transaction + auto acc2 = this->store->Access(); // write transaction // Create the vertex in transaction 2 { - auto vertex = acc2.CreateVertex(); + auto vertex = acc2->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 1U); - ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc2->Commit().HasError()); } - auto acc3 = store.Access(); // read transaction - auto acc4 = store.Access(); // write transaction + auto acc3 = this->store->Access(); // read transaction + auto acc4 = this->store->Access(); // write transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); // Delete the vertex in transaction 4 { - auto vertex = acc4.FindVertex(gid, memgraph::storage::View::NEW); + auto vertex = acc4->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 1U); - auto res = acc4.DeleteVertex(&*vertex); + auto res = acc4->DeleteVertex(&*vertex); ASSERT_TRUE(res.HasValue()); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 0U); - acc4.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 0U); + acc4->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc4.Commit().HasError()); + ASSERT_FALSE(acc4->Commit().HasError()); } - auto acc5 = store.Access(); // read transaction + auto acc5 = this->store->Access(); // read transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_FALSE(acc5.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc5.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc5->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc5->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::NEW), 0U); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteAbort) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteAbort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); - auto acc1 = store.Access(); // read transaction - auto acc2 = store.Access(); // write transaction + auto acc1 = this->store->Access(); // read transaction + auto acc2 = this->store->Access(); // write transaction // Create the vertex in transaction 2 { - auto vertex = acc2.CreateVertex(); + auto vertex = acc2->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 1U); - ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 1U); + ASSERT_FALSE(acc2->Commit().HasError()); } - auto acc3 = store.Access(); // read transaction - auto acc4 = store.Access(); // write transaction (aborted) + auto acc3 = this->store->Access(); // read transaction + auto acc4 = this->store->Access(); // write transaction (aborted) // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); // Delete the vertex in transaction 4, but abort the transaction { - auto vertex = acc4.FindVertex(gid, memgraph::storage::View::NEW); + auto vertex = acc4->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 1U); - auto res = acc4.DeleteVertex(&*vertex); + auto res = acc4->DeleteVertex(&*vertex); ASSERT_TRUE(res.HasValue()); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 0U); - acc4.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc4, memgraph::storage::View::NEW), 0U); + acc4->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc4, memgraph::storage::View::NEW), 0U); - acc4.Abort(); + acc4->Abort(); } - auto acc5 = store.Access(); // read transaction - auto acc6 = store.Access(); // write transaction + auto acc5 = this->store->Access(); // read transaction + auto acc6 = this->store->Access(); // write transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_TRUE(acc5.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc5.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc5->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc5->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::NEW), 1U); // Delete the vertex in transaction 6 { - auto vertex = acc6.FindVertex(gid, memgraph::storage::View::NEW); + auto vertex = acc6->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::NEW), 1U); - auto res = acc6.DeleteVertex(&*vertex); + auto res = acc6->DeleteVertex(&*vertex); ASSERT_TRUE(res.HasValue()); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::NEW), 0U); - acc6.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc6, memgraph::storage::View::NEW), 0U); + acc6->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc6, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc6.Commit().HasError()); + ASSERT_FALSE(acc6->Commit().HasError()); } - auto acc7 = store.Access(); // read transaction + auto acc7 = this->store->Access(); // read transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc3, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_TRUE(acc5.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::OLD), 1U); - ASSERT_TRUE(acc5.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc5, memgraph::storage::View::NEW), 1U); + ASSERT_TRUE(acc5->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::OLD), 1U); + ASSERT_TRUE(acc5->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc5, memgraph::storage::View::NEW), 1U); // Check whether the vertex exists in transaction 7 - ASSERT_FALSE(acc7.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc7, memgraph::storage::View::OLD), 0U); - ASSERT_FALSE(acc7.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc7, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc7->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc7, memgraph::storage::View::OLD), 0U); + ASSERT_FALSE(acc7->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc7, memgraph::storage::View::NEW), 0U); // Commit all accessors - ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc3.Commit().HasError()); - ASSERT_FALSE(acc5.Commit().HasError()); - ASSERT_FALSE(acc7.Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); + ASSERT_FALSE(acc3->Commit().HasError()); + ASSERT_FALSE(acc5->Commit().HasError()); + ASSERT_FALSE(acc7->Commit().HasError()); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteSerializationError) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteSerializationError) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); // Delete vertex in accessor 1 { - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 1U); { - auto res = acc1.DeleteVertex(&*vertex); + auto res = acc1->DeleteVertex(&*vertex); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); } { - auto res = acc1.DeleteVertex(&*vertex); + auto res = acc1->DeleteVertex(&*vertex); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); } - acc1.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc1, memgraph::storage::View::NEW), 0U); + acc1->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc1, memgraph::storage::View::NEW), 0U); } // Delete vertex in accessor 2 { - auto vertex = acc2.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc2->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 1U); - auto res = acc2.DeleteVertex(&*vertex); - ASSERT_TRUE(res.HasError()); - ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 1U); - acc2.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::OLD), 1U); - EXPECT_EQ(CountVertices(acc2, memgraph::storage::View::NEW), 1U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 1U); + auto res = acc2->DeleteVertex(&*vertex); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + // Serialization error for disk will be on commit + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); + } + + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 1U); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + // Beucase of pessimistic Serialization error happened on DeleteVertex() function + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 1U); + } else { + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 0U); + } + + acc2->AdvanceCommand(); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 1U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 1U); + } else { + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc2, memgraph::storage::View::NEW), 0U); + } } // Finalize both accessors - ASSERT_FALSE(acc1.Commit().HasError()); - acc2.Abort(); + ASSERT_FALSE(acc1->Commit().HasError()); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + acc2->Abort(); + } else { + auto res = acc2->Commit(); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(std::get<memgraph::storage::SerializationError>(res.GetError()), memgraph::storage::SerializationError()); + } // Check whether the vertex exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_FALSE(vertex); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc.Commit().HasError()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteSpecialCases) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) { memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex and delete it in the same transaction, but abort the // transaction { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid1 = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - auto res = acc.DeleteVertex(&vertex); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + auto res = acc->DeleteVertex(&vertex); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.Abort(); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->Abort(); } // Create vertex and delete it in the same transaction { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid2 = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 1U); - auto res = acc.DeleteVertex(&vertex); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 1U); + auto res = acc->DeleteVertex(&vertex); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.AdvanceCommand(); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - ASSERT_FALSE(acc.Commit().HasError()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->AdvanceCommand(); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the vertices exist { - auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid1, memgraph::storage::View::NEW).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, memgraph::storage::View::NEW).has_value()); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::OLD), 0U); - EXPECT_EQ(CountVertices(acc, memgraph::storage::View::NEW), 0U); - acc.Abort(); + auto acc = this->store->Access(); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); + ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); + ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::NEW).has_value()); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); + EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::NEW), 0U); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteLabel) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteLabel) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + ASSERT_FALSE(acc->Commit().HasError()); } // Add label, delete the vertex and check the label API (same command) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::NEW); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); // Check whether label 5 exists ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -620,7 +656,7 @@ TEST(StorageV2, VertexDeleteLabel) { } // Delete the vertex - ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -643,16 +679,16 @@ TEST(StorageV2, VertexDeleteLabel) { ASSERT_EQ(ret.GetError(), memgraph::storage::Error::DELETED_OBJECT); } - acc.Abort(); + acc->Abort(); } // Add label, delete the vertex and check the label API (different command) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::NEW); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); // Check whether label 5 exists ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -674,7 +710,7 @@ TEST(StorageV2, VertexDeleteLabel) { } // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 exists ASSERT_TRUE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -691,7 +727,7 @@ TEST(StorageV2, VertexDeleteLabel) { } // Delete the vertex - ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists ASSERT_TRUE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -705,7 +741,7 @@ TEST(StorageV2, VertexDeleteLabel) { ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 exists ASSERT_EQ(vertex->HasLabel(label, memgraph::storage::View::OLD).GetError(), @@ -729,32 +765,31 @@ TEST(StorageV2, VertexDeleteLabel) { ASSERT_EQ(ret.GetError(), memgraph::storage::Error::DELETED_OBJECT); } - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexDeleteProperty) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexDeleteProperty) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW).has_value()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); + ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); + ASSERT_FALSE(acc->Commit().HasError()); } // Set property, delete the vertex and check the property API (same command) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::NEW); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); // Check whether property 5 exists ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); @@ -776,7 +811,7 @@ TEST(StorageV2, VertexDeleteProperty) { } // Delete the vertex - ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); @@ -792,17 +827,17 @@ TEST(StorageV2, VertexDeleteProperty) { ASSERT_EQ(ret.GetError(), memgraph::storage::Error::DELETED_OBJECT); } - acc.Abort(); + acc->Abort(); } // Set property, delete the vertex and check the property API (different // command) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::NEW); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); // Check whether property 5 exists ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); @@ -824,7 +859,7 @@ TEST(StorageV2, VertexDeleteProperty) { } // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether property 5 exists ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); @@ -841,7 +876,7 @@ TEST(StorageV2, VertexDeleteProperty) { } // Delete the vertex - ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + ASSERT_TRUE(acc->DeleteVertex(&*vertex).GetValue()); // Check whether property 5 exists ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); @@ -855,7 +890,7 @@ TEST(StorageV2, VertexDeleteProperty) { ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether property 5 exists ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD).GetError(), @@ -872,20 +907,20 @@ TEST(StorageV2, VertexDeleteProperty) { ASSERT_EQ(ret.GetError(), memgraph::storage::Error::DELETED_OBJECT); } - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexLabelCommit) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexLabelCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex.HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex.Labels(memgraph::storage::View::NEW)->size(), 0); @@ -909,14 +944,15 @@ TEST(StorageV2, VertexLabelCommit) { ASSERT_FALSE(res.GetValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); + spdlog::debug("Commit done"); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_TRUE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); { @@ -932,19 +968,20 @@ TEST(StorageV2, VertexLabelCommit) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); + spdlog::debug("Abort done"); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); { auto res = vertex->RemoveLabel(label); @@ -968,49 +1005,50 @@ TEST(StorageV2, VertexLabelCommit) { ASSERT_FALSE(res.GetValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); + spdlog::debug("Commit done"); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); + spdlog::debug("Abort done"); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexLabelAbort) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexLabelAbort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Add label 5, but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); @@ -1034,37 +1072,37 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_FALSE(res.GetValue()); } - acc.Abort(); + acc->Abort(); } // Check that label 5 doesn't exist. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); } // Add label 5. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); @@ -1088,16 +1126,16 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_FALSE(res.GetValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that label 5 exists. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_TRUE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); { @@ -1113,21 +1151,21 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); } // Remove label 5, but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); { auto res = vertex->RemoveLabel(label); @@ -1151,16 +1189,16 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_FALSE(res.GetValue()); } - acc.Abort(); + acc->Abort(); } // Check that label 5 exists. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_TRUE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); { @@ -1176,21 +1214,21 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); } // Remove label 5. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); { auto res = vertex->RemoveLabel(label); @@ -1214,52 +1252,51 @@ TEST(StorageV2, VertexLabelAbort) { ASSERT_FALSE(res.GetValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that label 5 doesn't exist. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = acc->NameToLabel("label5"); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, memgraph::storage::View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = acc->NameToLabel("other"); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, memgraph::storage::View::NEW).GetValue()); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexLabelSerializationError) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexLabelSerializationError) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); // Add label 1 in accessor 1. { - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc1.NameToLabel("label1"); - auto label2 = acc1.NameToLabel("label2"); + auto label1 = acc1->NameToLabel("label1"); + auto label2 = acc1->NameToLabel("label2"); ASSERT_FALSE(vertex->HasLabel(label1, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label1, memgraph::storage::View::NEW).GetValue()); @@ -1294,11 +1331,11 @@ TEST(StorageV2, VertexLabelSerializationError) { // Add label 2 in accessor 2. { - auto vertex = acc2.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc2->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc2.NameToLabel("label1"); - auto label2 = acc2.NameToLabel("label2"); + auto label1 = acc2->NameToLabel("label1"); + auto label2 = acc2->NameToLabel("label2"); ASSERT_FALSE(vertex->HasLabel(label1, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label1, memgraph::storage::View::NEW).GetValue()); @@ -1308,24 +1345,38 @@ TEST(StorageV2, VertexLabelSerializationError) { ASSERT_EQ(vertex->Labels(memgraph::storage::View::NEW)->size(), 0); { - auto res = vertex->AddLabel(label1); - ASSERT_TRUE(res.HasError()); - ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); + auto res = vertex->AddLabel(label2); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + // InMemoryStorage works with pessimistic transactions. + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); + } else { + // Disk storage works with optimistic transactions. + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } } } // Finalize both accessors. - ASSERT_FALSE(acc1.Commit().HasError()); - acc2.Abort(); + ASSERT_FALSE(acc1->Commit().HasError()); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + acc2->Abort(); + } else { + // Disk storage works with optimistic transactions. So on write conflict, transaction fails on commit. + auto res = acc2->Commit(); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(std::get<memgraph::storage::SerializationError>(res.GetError()), memgraph::storage::SerializationError()); + } // Check which labels exist. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc.NameToLabel("label1"); - auto label2 = acc.NameToLabel("label2"); + auto label1 = acc->NameToLabel("label1"); + auto label2 = acc->NameToLabel("label2"); ASSERT_TRUE(vertex->HasLabel(label1, memgraph::storage::View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label2, memgraph::storage::View::OLD).GetValue()); @@ -1343,20 +1394,19 @@ TEST(StorageV2, VertexLabelSerializationError) { ASSERT_EQ(labels[0], label1); } - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexPropertyCommit) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexPropertyCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex.Properties(memgraph::storage::View::NEW)->size(), 0); @@ -1387,14 +1437,14 @@ TEST(StorageV2, VertexPropertyCommit) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -1410,19 +1460,19 @@ TEST(StorageV2, VertexPropertyCommit) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); { auto old_value = vertex->SetProperty(property, memgraph::storage::PropertyValue()); @@ -1446,49 +1496,48 @@ TEST(StorageV2, VertexPropertyCommit) { ASSERT_TRUE(old_value->IsNull()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexPropertyAbort) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexPropertyAbort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Set property 5 to "nandare", but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); @@ -1519,37 +1568,37 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - acc.Abort(); + acc->Abort(); } // Check that property 5 is null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); @@ -1580,16 +1629,16 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that property 5 is "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -1605,21 +1654,21 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to null, but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -1651,16 +1700,16 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check that property 5 is "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -1676,21 +1725,21 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(vertex->GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -1722,52 +1771,51 @@ TEST(StorageV2, VertexPropertyAbort) { ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that property 5 is null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexPropertySerializationError) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexPropertySerializationError) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); // Set property 1 to 123 in accessor 1. { - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc1.NameToProperty("property1"); - auto property2 = acc1.NameToProperty("property2"); + auto property1 = acc1->NameToProperty("property1"); + auto property2 = acc1->NameToProperty("property2"); ASSERT_TRUE(vertex->GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); @@ -1796,11 +1844,11 @@ TEST(StorageV2, VertexPropertySerializationError) { // Set property 2 to "nandare" in accessor 2. { - auto vertex = acc2.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc2->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc2.NameToProperty("property1"); - auto property2 = acc2.NameToProperty("property2"); + auto property1 = acc2->NameToProperty("property1"); + auto property2 = acc2->NameToProperty("property2"); ASSERT_TRUE(vertex->GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); @@ -1811,23 +1859,37 @@ TEST(StorageV2, VertexPropertySerializationError) { { auto res = vertex->SetProperty(property2, memgraph::storage::PropertyValue("nandare")); - ASSERT_TRUE(res.HasError()); - ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + // InMemoryStorage works with pessimistic transactions. + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); + } else { + // Disk storage works with optimistic transactions. + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res->IsNull()); + } } } // Finalize both accessors. - ASSERT_FALSE(acc1.Commit().HasError()); - acc2.Abort(); + ASSERT_FALSE(acc1->Commit().HasError()); + if (std::is_same<TypeParam, memgraph::storage::InMemoryStorage>::value) { + acc2->Abort(); + } else { + // Disk storage works with optimistic transactions. So on write conflict, transaction fails on commit. + auto res = acc2->Commit(); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(std::get<memgraph::storage::SerializationError>(res.GetError()), memgraph::storage::SerializationError()); + } // Check which properties exist. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc.NameToProperty("property1"); - auto property2 = acc.NameToProperty("property2"); + auto property1 = acc->NameToProperty("property1"); + auto property2 = acc->NameToProperty("property2"); ASSERT_EQ(vertex->GetProperty(property1, memgraph::storage::View::OLD)->ValueInt(), 123); ASSERT_TRUE(vertex->GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); @@ -1845,18 +1907,17 @@ TEST(StorageV2, VertexPropertySerializationError) { ASSERT_EQ(properties[property1].ValueInt(), 123); } - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, VertexLabelPropertyMixed) { - memgraph::storage::Storage store; - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); +TYPED_TEST(StorageV2Test, VertexLabelPropertyMixed) { + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); - auto label = acc.NameToLabel("label5"); - auto property = acc.NameToProperty("property5"); + auto label = acc->NameToLabel("label5"); + auto property = acc->NameToProperty("property5"); // Check whether label 5 and property 5 exist ASSERT_FALSE(vertex.HasLabel(label, memgraph::storage::View::NEW).GetValue()); @@ -1878,7 +1939,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) { ASSERT_EQ(vertex.Properties(memgraph::storage::View::NEW)->size(), 0); // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -1924,7 +1985,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) { } // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -1982,7 +2043,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) { } // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -2036,7 +2097,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) { } // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 and property 5 exist ASSERT_FALSE(vertex.HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -2074,7 +2135,7 @@ TEST(StorageV2, VertexLabelPropertyMixed) { ASSERT_EQ(vertex.Properties(memgraph::storage::View::NEW)->size(), 0); // Advance command - acc.AdvanceCommand(); + acc->AdvanceCommand(); // Check whether label 5 and property 5 exist ASSERT_FALSE(vertex.HasLabel(label, memgraph::storage::View::OLD).GetValue()); @@ -2086,28 +2147,27 @@ TEST(StorageV2, VertexLabelPropertyMixed) { ASSERT_EQ(vertex.Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(vertex.Properties(memgraph::storage::View::NEW)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } -TEST(StorageV2, VertexPropertyClear) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexPropertyClear) { memgraph::storage::Gid gid; - auto property1 = store.NameToProperty("property1"); - auto property2 = store.NameToProperty("property2"); + auto property1 = this->store->NameToProperty("property1"); + auto property2 = this->store->NameToProperty("property2"); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto old_value = vertex.SetProperty(property1, memgraph::storage::PropertyValue("value")); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_EQ(vertex->GetProperty(property1, memgraph::storage::View::OLD)->ValueString(), "value"); @@ -2135,22 +2195,22 @@ TEST(StorageV2, VertexPropertyClear) { ASSERT_TRUE(vertex->GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto old_value = vertex->SetProperty(property2, memgraph::storage::PropertyValue(42)); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_EQ(vertex->GetProperty(property1, memgraph::storage::View::OLD)->ValueString(), "value"); @@ -2179,29 +2239,27 @@ TEST(StorageV2, VertexPropertyClear) { ASSERT_TRUE(vertex->GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(vertex->GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - acc.Abort(); + acc->Abort(); } } -TEST(StorageV2, VertexNonexistentLabelPropertyEdgeAPI) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexNonexistentLabelPropertyEdgeAPI) { + auto label = this->store->NameToLabel("label"); + auto property = this->store->NameToProperty("property"); - auto label = store.NameToLabel("label"); - auto property = store.NameToProperty("property"); - - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); // Check state before (OLD view). ASSERT_EQ(vertex.Labels(memgraph::storage::View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); @@ -2228,7 +2286,7 @@ TEST(StorageV2, VertexNonexistentLabelPropertyEdgeAPI) { // Modify vertex. ASSERT_TRUE(vertex.AddLabel(label).HasValue()); ASSERT_TRUE(vertex.SetProperty(property, memgraph::storage::PropertyValue("value")).HasValue()); - ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")).HasValue()); + ASSERT_TRUE(acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge")).HasValue()); // Check state after (OLD view). ASSERT_EQ(vertex.Labels(memgraph::storage::View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); @@ -2252,325 +2310,320 @@ TEST(StorageV2, VertexNonexistentLabelPropertyEdgeAPI) { ASSERT_EQ(*vertex.InDegree(memgraph::storage::View::NEW), 1); ASSERT_EQ(*vertex.OutDegree(memgraph::storage::View::NEW), 1); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } -TEST(StorageV2, VertexVisibilitySingleTransaction) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexVisibilitySingleTransaction) { + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto acc1 = store.Access(); - auto acc2 = store.Access(); - - auto vertex = acc1.CreateVertex(); + auto vertex = acc1->CreateVertex(); auto gid = vertex.Gid(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_TRUE(vertex.AddLabel(acc1.NameToLabel("label")).HasValue()); + ASSERT_TRUE(vertex.AddLabel(acc1->NameToLabel("label")).HasValue()); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_TRUE(vertex.SetProperty(acc1.NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex.SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); - auto acc3 = store.Access(); + auto acc3 = this->store->Access(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_TRUE(acc1.DeleteVertex(&vertex).HasValue()); + ASSERT_TRUE(acc1->DeleteVertex(&vertex).HasValue()); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); - acc3.AdvanceCommand(); + acc1->AdvanceCommand(); + acc3->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.Abort(); - acc2.Abort(); - acc3.Abort(); + acc1->Abort(); + acc2->Abort(); + acc3->Abort(); } -TEST(StorageV2, VertexVisibilityMultipleTransactions) { - memgraph::storage::Storage store; +TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { memgraph::storage::Gid gid; { - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto vertex = acc1.CreateVertex(); + auto vertex = acc1->CreateVertex(); gid = vertex.Gid(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - acc2.AdvanceCommand(); + acc2->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); + acc1->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); + ASSERT_FALSE(acc2->Commit().HasError()); } { - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_TRUE(vertex->AddLabel(acc1.NameToLabel("label")).HasValue()); + ASSERT_TRUE(vertex->AddLabel(acc1->NameToLabel("label")).HasValue()); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); + acc1->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - acc2.AdvanceCommand(); + acc2->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_TRUE(vertex->SetProperty(acc1.NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex->SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); - auto acc3 = store.Access(); + auto acc3 = this->store->Access(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); + acc1->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc2.AdvanceCommand(); + acc2->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc3.AdvanceCommand(); + acc3->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc2.Commit().HasError()); - ASSERT_FALSE(acc3.Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); + ASSERT_FALSE(acc2->Commit().HasError()); + ASSERT_FALSE(acc3->Commit().HasError()); } { - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); + ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue()); - auto acc3 = store.Access(); + auto acc3 = this->store->Access(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc2.AdvanceCommand(); + acc2->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); + acc1->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc3.AdvanceCommand(); + acc3->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.Abort(); - acc2.Abort(); - acc3.Abort(); + acc1->Abort(); + acc2->Abort(); + acc3->Abort(); } { - auto acc = store.Access(); + auto acc = this->store->Access(); - EXPECT_TRUE(acc.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_TRUE(acc.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW)); - acc.Abort(); + acc->Abort(); } { - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = this->store->Access(); + auto acc2 = this->store->Access(); - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); + ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue()); - auto acc3 = store.Access(); + auto acc3 = this->store->Access(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc2.AdvanceCommand(); + acc2->AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc1.AdvanceCommand(); + acc1->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - acc3.AdvanceCommand(); + acc3->AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, memgraph::storage::View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc2->FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW)); - ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc2.Commit().HasError()); - ASSERT_FALSE(acc3.Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); + ASSERT_FALSE(acc2->Commit().HasError()); + ASSERT_FALSE(acc3->Commit().HasError()); } { - auto acc = store.Access(); + auto acc = this->store->Access(); - EXPECT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_FALSE(acc.FindVertex(gid, memgraph::storage::View::OLD)); - EXPECT_FALSE(acc.FindVertex(gid, memgraph::storage::View::NEW)); + EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD)); + EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW)); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV2, DeletedVertexAccessor) { - memgraph::storage::Storage store; - - const auto property = store.NameToProperty("property"); +TYPED_TEST(StorageV2Test, DeletedVertexAccessor) { + const auto property = this->store->NameToProperty("property"); const memgraph::storage::PropertyValue property_value{"property_value"}; std::optional<memgraph::storage::Gid> gid; // Create the vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(vertex.SetProperty(property, property_value).HasError()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - auto acc = store.Access(); - auto vertex = acc.FindVertex(*gid, memgraph::storage::View::OLD); + auto acc = this->store->Access(); + auto vertex = acc->FindVertex(*gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - auto maybe_deleted_vertex = acc.DeleteVertex(&*vertex); + auto maybe_deleted_vertex = acc->DeleteVertex(&*vertex); ASSERT_FALSE(maybe_deleted_vertex.HasError()); auto deleted_vertex = maybe_deleted_vertex.GetValue(); @@ -2582,7 +2635,7 @@ TEST(StorageV2, DeletedVertexAccessor) { const auto maybe_property = deleted_vertex->GetProperty(property, memgraph::storage::View::OLD); ASSERT_FALSE(maybe_property.HasError()); ASSERT_EQ(property_value, *maybe_property); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); { // you can call read only methods and get valid results even after the diff --git a/tests/unit/storage_v2_constraints.cpp b/tests/unit/storage_v2_constraints.cpp index aadc98ab0..8094c4ab4 100644 --- a/tests/unit/storage_v2_constraints.cpp +++ b/tests/unit/storage_v2_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,338 +11,379 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <filesystem> #include <variant> -#include "storage/v2/storage.hpp" +#include "storage/v2/constraints/constraints.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/disk/unique_constraints.hpp" +#include "storage/v2/inmemory/storage.hpp" + +#include "disk_test_utils.hpp" // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; +using testing::Types; using testing::UnorderedElementsAre; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define ASSERT_NO_ERROR(result) ASSERT_FALSE((result).HasError()) +template <typename StorageType> class ConstraintsTest : public testing::Test { - protected: - ConstraintsTest() - : prop1(storage.NameToProperty("prop1")), - prop2(storage.NameToProperty("prop2")), - label1(storage.NameToLabel("label1")), - label2(storage.NameToLabel("label2")) {} + public: + const std::string testSuite = "storage_v2_constraints"; - Storage storage; + ConstraintsTest() { + /// TODO: andi How to make this better? Because currentlly for every test changed you need to create a configuration + config_ = disk_test_utils::GenerateOnDiskConfig(testSuite); + storage = std::make_unique<StorageType>(config_); + prop1 = storage->NameToProperty("prop1"); + prop2 = storage->NameToProperty("prop2"); + label1 = storage->NameToLabel("label1"); + label2 = storage->NameToLabel("label2"); + } + + void TearDown() override { + storage.reset(nullptr); + + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + } + + std::unique_ptr<Storage> storage; + memgraph::storage::Config config_; PropertyId prop1; PropertyId prop2; LabelId label1; LabelId label2; }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(ConstraintsTest, StorageTypes); + // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { - EXPECT_EQ(storage.ListAllConstraints().existence.size(), 0); +TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { + EXPECT_EQ(this->storage->ListAllConstraints().existence.size(), 0); { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); EXPECT_FALSE(res.HasError()); } - EXPECT_THAT(storage.ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(label1, prop1))); + EXPECT_THAT(this->storage->ListAllConstraints().existence, + UnorderedElementsAre(std::make_pair(this->label1, this->prop1))); { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); EXPECT_TRUE(res.HasError()); } - EXPECT_THAT(storage.ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(label1, prop1))); + EXPECT_THAT(this->storage->ListAllConstraints().existence, + UnorderedElementsAre(std::make_pair(this->label1, this->prop1))); { - auto res = storage.CreateExistenceConstraint(label2, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label2, this->prop1, {}); EXPECT_FALSE(res.HasError()); } - EXPECT_THAT(storage.ListAllConstraints().existence, - UnorderedElementsAre(std::make_pair(label1, prop1), std::make_pair(label2, prop1))); - EXPECT_FALSE(storage.DropExistenceConstraint(label1, prop1).HasError()); - EXPECT_TRUE(storage.DropExistenceConstraint(label1, prop1).HasError()); - EXPECT_THAT(storage.ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(label2, prop1))); - EXPECT_FALSE(storage.DropExistenceConstraint(label2, prop1).HasError()); - EXPECT_TRUE(storage.DropExistenceConstraint(label2, prop2).HasError()); - EXPECT_EQ(storage.ListAllConstraints().existence.size(), 0); + EXPECT_THAT( + this->storage->ListAllConstraints().existence, + UnorderedElementsAre(std::make_pair(this->label1, this->prop1), std::make_pair(this->label2, this->prop1))); + EXPECT_FALSE(this->storage->DropExistenceConstraint(this->label1, this->prop1, {}).HasError()); + EXPECT_TRUE(this->storage->DropExistenceConstraint(this->label1, this->prop1, {}).HasError()); + EXPECT_THAT(this->storage->ListAllConstraints().existence, + UnorderedElementsAre(std::make_pair(this->label2, this->prop1))); + EXPECT_FALSE(this->storage->DropExistenceConstraint(this->label2, this->prop1, {}).HasError()); + EXPECT_TRUE(this->storage->DropExistenceConstraint(this->label2, this->prop2, {}).HasError()); + EXPECT_EQ(this->storage->ListAllConstraints().existence.size(), 0); { - auto res = storage.CreateExistenceConstraint(label2, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label2, this->prop1, {}); EXPECT_FALSE(res.HasError()); } - EXPECT_THAT(storage.ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(label2, prop1))); + EXPECT_THAT(this->storage->ListAllConstraints().existence, + UnorderedElementsAre(std::make_pair(this->label2, this->prop1))); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, ExistenceConstraintsCreateFailure1) { +TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) { { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + auto acc = this->storage->Access(); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); EXPECT_FALSE(res.HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, ExistenceConstraintsCreateFailure2) { +TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure2) { { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); + auto acc = this->storage->Access(); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); EXPECT_FALSE(res.HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { +TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { { - auto res = storage.CreateExistenceConstraint(label1, prop1); + auto res = this->storage->CreateExistenceConstraint(this->label1, this->prop1, {}); EXPECT_FALSE(res.HasError()); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); - auto res = acc.Commit(); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue())); + auto acc = this->storage->Access(); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue())); } - auto res = acc.Commit(); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::EXISTENCE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue())); + auto acc = this->storage->Access(); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue())); } - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } - ASSERT_FALSE(storage.DropExistenceConstraint(label1, prop1).HasError()); + ASSERT_FALSE(this->storage->DropExistenceConstraint(this->label1, this->prop1, {}).HasError()); { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(acc->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { - EXPECT_EQ(storage.ListAllConstraints().unique.size(), 0); +TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { + EXPECT_EQ(this->storage->ListAllConstraints().unique.size(), 0); { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); EXPECT_TRUE(res.HasValue()); EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } - EXPECT_THAT(storage.ListAllConstraints().unique, - UnorderedElementsAre(std::make_pair(label1, std::set<PropertyId>{prop1}))); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}))); { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); EXPECT_TRUE(res.HasValue()); EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::ALREADY_EXISTS); } - EXPECT_THAT(storage.ListAllConstraints().unique, - UnorderedElementsAre(std::make_pair(label1, std::set<PropertyId>{prop1}))); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}))); { - auto res = storage.CreateUniqueConstraint(label2, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label2, {this->prop1}, {}); EXPECT_TRUE(res.HasValue() && res.GetValue() == UniqueConstraints::CreationStatus::SUCCESS); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } - EXPECT_THAT(storage.ListAllConstraints().unique, - UnorderedElementsAre(std::make_pair(label1, std::set<PropertyId>{prop1}), - std::make_pair(label2, std::set<PropertyId>{prop1}))); - EXPECT_EQ(storage.DropUniqueConstraint(label1, {prop1}).GetValue(), UniqueConstraints::DeletionStatus::SUCCESS); - EXPECT_EQ(storage.DropUniqueConstraint(label1, {prop1}).GetValue(), UniqueConstraints::DeletionStatus::NOT_FOUND); - EXPECT_THAT(storage.ListAllConstraints().unique, - UnorderedElementsAre(std::make_pair(label2, std::set<PropertyId>{prop1}))); - EXPECT_EQ(storage.DropUniqueConstraint(label2, {prop1}).GetValue(), UniqueConstraints::DeletionStatus::SUCCESS); - EXPECT_EQ(storage.DropUniqueConstraint(label2, {prop2}).GetValue(), UniqueConstraints::DeletionStatus::NOT_FOUND); - EXPECT_EQ(storage.ListAllConstraints().unique.size(), 0); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}), + std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); + EXPECT_EQ(this->storage->DropUniqueConstraint(this->label1, {this->prop1}, {}).GetValue(), + UniqueConstraints::DeletionStatus::SUCCESS); + EXPECT_EQ(this->storage->DropUniqueConstraint(this->label1, {this->prop1}, {}).GetValue(), + UniqueConstraints::DeletionStatus::NOT_FOUND); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); + EXPECT_EQ(this->storage->DropUniqueConstraint(this->label2, {this->prop1}, {}).GetValue(), + UniqueConstraints::DeletionStatus::SUCCESS); + EXPECT_EQ(this->storage->DropUniqueConstraint(this->label2, {this->prop2}, {}).GetValue(), + UniqueConstraints::DeletionStatus::NOT_FOUND); + EXPECT_EQ(this->storage->ListAllConstraints().unique.size(), 0); { - auto res = storage.CreateUniqueConstraint(label2, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label2, {this->prop1}, {}); EXPECT_TRUE(res.HasValue()); EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } - EXPECT_THAT(storage.ListAllConstraints().unique, - UnorderedElementsAre(std::make_pair(label2, std::set<PropertyId>{prop1}))); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure1) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) { { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 2; ++i) { - auto vertex1 = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); + auto vertex1 = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + auto acc = this->storage->Access(); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsCreateFailure2) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure2) { { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 2; ++i) { - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); } { - auto acc = storage.Access(); + auto acc = this->storage->Access(); int value = 0; - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(value))); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(value))); ++value; } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsNoViolation1) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) { Gid gid1; Gid gid2; { - auto acc = storage.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); - auto vertex1 = acc.FindVertex(gid1, View::OLD); - auto vertex2 = acc.FindVertex(gid2, View::OLD); + auto acc = this->storage->Access(); + auto vertex1 = acc->FindVertex(gid1, View::OLD); + auto vertex2 = acc->FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex1->SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(vertex2->AddLabel(label1)); - ASSERT_NO_ERROR(vertex2->SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue(3))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(vertex1->SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop2, PropertyValue(3))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex1 = acc.FindVertex(gid1, View::OLD); - auto vertex2 = acc.FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex1->SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex2->SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex1 = acc->FindVertex(gid1, View::OLD); + auto vertex2 = acc->FindVertex(gid2, View::OLD); + ASSERT_NO_ERROR(vertex1->SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsNoViolation2) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation2) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -351,28 +392,28 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation2) { // tx1: B---SP(v1, 1)---SP(v1, 2)---OK-- // tx2: -B---SP(v2, 2)---SP(v2, 1)---OK- - auto acc1 = storage.Access(); - auto acc2 = storage.Access(); - auto vertex1 = acc1.CreateVertex(); - auto vertex2 = acc2.CreateVertex(); + auto acc1 = this->storage->Access(); + auto acc2 = this->storage->Access(); + auto vertex1 = acc1->CreateVertex(); + auto vertex2 = acc2->CreateVertex(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc1.Commit()); - ASSERT_NO_ERROR(acc2.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); + ASSERT_NO_ERROR(acc2->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsNoViolation3) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation3) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -382,33 +423,33 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation3) { // tx2: --------------------B---SP(v1, 2)---OK-- // tx3: ---------------------B---SP(v2, 1)---OK- - auto acc1 = storage.Access(); - auto vertex1 = acc1.CreateVertex(); + auto acc1 = this->storage->Access(); + auto vertex1 = acc1->CreateVertex(); auto gid = vertex1.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc1.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = storage.Access(); - auto acc3 = storage.Access(); - auto vertex2 = acc2.FindVertex(gid, View::NEW); // vertex1 == vertex2 - auto vertex3 = acc3.CreateVertex(); + auto acc2 = this->storage->Access(); + auto acc3 = this->storage->Access(); + auto vertex2 = acc2->FindVertex(gid, View::NEW); // vertex1 == vertex2 + auto vertex3 = acc3->CreateVertex(); - ASSERT_NO_ERROR(vertex2->SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex3.AddLabel(label1)); - ASSERT_NO_ERROR(vertex3.SetProperty(prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex3.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex3.SetProperty(this->prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc2.Commit()); - ASSERT_NO_ERROR(acc3.Commit()); + ASSERT_NO_ERROR(acc2->Commit()); + ASSERT_NO_ERROR(acc3->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsNoViolation4) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation4) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -418,56 +459,57 @@ TEST_F(ConstraintsTest, UniqueConstraintsNoViolation4) { // tx2: --------------------B---SP(v2, 1)-----OK- // tx3: ---------------------B---SP(v1, 2)---OK-- - auto acc1 = storage.Access(); - auto vertex1 = acc1.CreateVertex(); + auto acc1 = this->storage->Access(); + auto vertex1 = acc1->CreateVertex(); auto gid = vertex1.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc1.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = storage.Access(); - auto acc3 = storage.Access(); - auto vertex2 = acc2.CreateVertex(); - auto vertex3 = acc3.FindVertex(gid, View::NEW); + auto acc2 = this->storage->Access(); + auto acc3 = this->storage->Access(); + auto vertex2 = acc2->CreateVertex(); + auto vertex3 = acc3->FindVertex(gid, View::NEW); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex3->SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex3->SetProperty(this->prop1, PropertyValue(2))); - ASSERT_NO_ERROR(acc3.Commit()); - ASSERT_NO_ERROR(acc2.Commit()); + ASSERT_NO_ERROR(acc3->Commit()); + ASSERT_NO_ERROR(acc2->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit1) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit1) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1))); - auto res = acc.Commit(); + auto acc = this->storage->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(1))); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit2) { +/// TODO: andi consistency problems +TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit2) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -477,39 +519,41 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit2) { // tx2: -------------------------------B---SP(v1, 3)---OK---- // tx3: --------------------------------B---SP(v2, 3)---FAIL- - auto acc1 = storage.Access(); - auto vertex1 = acc1.CreateVertex(); - auto vertex2 = acc1.CreateVertex(); + auto acc1 = this->storage->Access(); + auto vertex1 = acc1->CreateVertex(); + auto vertex2 = acc1->CreateVertex(); auto gid1 = vertex1.Gid(); auto gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(2))); - ASSERT_NO_ERROR(acc1.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = storage.Access(); - auto acc3 = storage.Access(); - auto vertex3 = acc2.FindVertex(gid1, View::NEW); // vertex3 == vertex1 - auto vertex4 = acc3.FindVertex(gid2, View::NEW); // vertex4 == vertex2 + auto acc2 = this->storage->Access(); + auto acc3 = this->storage->Access(); + auto vertex3 = acc2->FindVertex(gid1, View::NEW); // vertex3 == vertex1 + auto vertex4 = acc3->FindVertex(gid2, View::NEW); // vertex4 == vertex2 - ASSERT_NO_ERROR(vertex3->SetProperty(prop1, PropertyValue(3))); - ASSERT_NO_ERROR(vertex4->SetProperty(prop1, PropertyValue(3))); + ASSERT_NO_ERROR(vertex3->SetProperty(this->prop1, PropertyValue(3))); + ASSERT_NO_ERROR(vertex4->SetProperty(this->prop1, PropertyValue(3))); - ASSERT_NO_ERROR(acc2.Commit()); - auto res = acc3.Commit(); + ASSERT_NO_ERROR(acc2->Commit()); + auto res = acc3->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit3) { +/// TODO: andi consistency problems +TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit3) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -519,46 +563,48 @@ TEST_F(ConstraintsTest, UniqueConstraintsViolationOnCommit3) { // tx2: -------------------------------B---SP(v1, 2)---FAIL-- // tx3: --------------------------------B---SP(v2, 1)---FAIL- - auto acc1 = storage.Access(); - auto vertex1 = acc1.CreateVertex(); - auto vertex2 = acc1.CreateVertex(); + auto acc1 = this->storage->Access(); + auto vertex1 = acc1->CreateVertex(); + auto vertex2 = acc1->CreateVertex(); auto gid1 = vertex1.Gid(); auto gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(2))); - ASSERT_NO_ERROR(acc1.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = storage.Access(); - auto acc3 = storage.Access(); - auto vertex3 = acc2.FindVertex(gid1, View::OLD); // vertex3 == vertex1 - auto vertex4 = acc3.FindVertex(gid2, View::OLD); // vertex4 == vertex2 + auto acc2 = this->storage->Access(); + auto acc3 = this->storage->Access(); + auto vertex3 = acc2->FindVertex(gid1, View::OLD); // vertex3 == vertex1 + auto vertex4 = acc3->FindVertex(gid2, View::OLD); // vertex4 == vertex2 - // Setting `prop2` shouldn't affect the remaining code. - ASSERT_NO_ERROR(vertex3->SetProperty(prop2, PropertyValue(3))); - ASSERT_NO_ERROR(vertex4->SetProperty(prop2, PropertyValue(3))); + // Setting `this->prop2` shouldn't affect the remaining code. + ASSERT_NO_ERROR(vertex3->SetProperty(this->prop2, PropertyValue(3))); + ASSERT_NO_ERROR(vertex4->SetProperty(this->prop2, PropertyValue(3))); - ASSERT_NO_ERROR(vertex3->SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex4->SetProperty(prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex3->SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex4->SetProperty(this->prop1, PropertyValue(1))); - auto res = acc2.Commit(); + auto res = acc2->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); - res = acc3.Commit(); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); + res = acc3->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set<PropertyId>{this->prop1}})); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -568,128 +614,132 @@ TEST_F(ConstraintsTest, UniqueConstraintsLabelAlteration) { { // B---AL(v2)---SP(v1, 1)---SP(v2, 1)---OK - auto acc = storage.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label2)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1))); + spdlog::debug("Vertex1 gid: {} Vertex2 gid: {}\n", memgraph::utils::SerializeIdType(gid1), + memgraph::utils::SerializeIdType(gid2)); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label2)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(1))); + + ASSERT_NO_ERROR(acc->Commit()); } { // tx1: B---AL(v1)-----OK- // tx2: -B---RL(v2)---OK-- - auto acc1 = storage.Access(); - auto acc2 = storage.Access(); - auto vertex1 = acc1.FindVertex(gid1, View::OLD); - auto vertex2 = acc2.FindVertex(gid2, View::OLD); + auto acc1 = this->storage->Access(); + auto acc2 = this->storage->Access(); + auto vertex1 = acc1->FindVertex(gid1, View::OLD); + auto vertex2 = acc2->FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex1->AddLabel(label1)); - ASSERT_NO_ERROR(vertex2->RemoveLabel(label1)); + ASSERT_NO_ERROR(vertex1->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->RemoveLabel(this->label1)); // Reapplying labels shouldn't affect the remaining code. - ASSERT_NO_ERROR(vertex1->RemoveLabel(label1)); - ASSERT_NO_ERROR(vertex2->AddLabel(label1)); - ASSERT_NO_ERROR(vertex1->AddLabel(label1)); - ASSERT_NO_ERROR(vertex2->RemoveLabel(label1)); - ASSERT_NO_ERROR(vertex1->RemoveLabel(label2)); + ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label2)); // Commit the second transaction. - ASSERT_NO_ERROR(acc2.Commit()); + ASSERT_NO_ERROR(acc2->Commit()); // Reapplying labels after first commit shouldn't affect the remaining code. - ASSERT_NO_ERROR(vertex1->RemoveLabel(label1)); - ASSERT_NO_ERROR(vertex1->AddLabel(label1)); + ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(vertex1->AddLabel(this->label1)); // Commit the first transaction. - ASSERT_NO_ERROR(acc1.Commit()); + ASSERT_NO_ERROR(acc1->Commit()); } { // B---AL(v2)---FAIL - auto acc = storage.Access(); - auto vertex2 = acc.FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex2->AddLabel(label1)); + auto acc = this->storage->Access(); + auto vertex2 = acc->FindVertex(gid2, View::OLD); + ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); - auto res = acc.Commit(); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set{prop1}})); + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set{this->prop1}})); } { // B---RL(v1)---OK - auto acc = storage.Access(); - auto vertex1 = acc.FindVertex(gid1, View::OLD); - ASSERT_NO_ERROR(vertex1->RemoveLabel(label1)); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex1 = acc->FindVertex(gid1, View::OLD); + ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(acc->Commit()); } { // tx1: B---AL(v1)-----FAIL // tx2: -B---AL(v2)---OK--- - auto acc1 = storage.Access(); - auto acc2 = storage.Access(); - auto vertex1 = acc1.FindVertex(gid1, View::OLD); - auto vertex2 = acc2.FindVertex(gid2, View::OLD); + auto acc1 = this->storage->Access(); + auto acc2 = this->storage->Access(); + auto vertex1 = acc1->FindVertex(gid1, View::OLD); + auto vertex2 = acc2->FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex1->AddLabel(label1)); - ASSERT_NO_ERROR(vertex2->AddLabel(label1)); + ASSERT_NO_ERROR(vertex1->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); // Reapply everything. - ASSERT_NO_ERROR(vertex1->RemoveLabel(label1)); - ASSERT_NO_ERROR(vertex2->RemoveLabel(label1)); - ASSERT_NO_ERROR(vertex1->AddLabel(label1)); - ASSERT_NO_ERROR(vertex2->AddLabel(label1)); + ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->RemoveLabel(this->label1)); + ASSERT_NO_ERROR(vertex1->AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); - ASSERT_NO_ERROR(acc2.Commit()); + ASSERT_NO_ERROR(acc2->Commit()); - auto res = acc1.Commit(); + auto res = acc1->Commit(); ASSERT_TRUE(res.HasError()); EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set{prop1}})); + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set{this->prop1}})); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsPropertySetSize) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsPropertySetSize) { { // This should fail since unique constraint cannot be created for an empty // property set. - auto res = storage.CreateUniqueConstraint(label1, {}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::EMPTY_PROPERTIES); } // Removing a constraint with empty property set should also fail. - ASSERT_EQ(storage.DropUniqueConstraint(label1, {}).GetValue(), UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES); + ASSERT_EQ(this->storage->DropUniqueConstraint(this->label1, {}, {}).GetValue(), + UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES); // Create a set of 33 properties. std::set<PropertyId> properties; for (int i = 1; i <= 33; ++i) { - properties.insert(storage.NameToProperty("prop" + std::to_string(i))); + properties.insert(this->storage->NameToProperty("prop" + std::to_string(i))); } { // This should fail since list of properties exceeds the maximum number of // properties, which is 32. - auto res = storage.CreateUniqueConstraint(label1, properties); + auto res = this->storage->CreateUniqueConstraint(this->label1, properties, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED); } // An attempt to delete constraint with too large property set should fail. - ASSERT_EQ(storage.DropUniqueConstraint(label1, properties).GetValue(), + ASSERT_EQ(this->storage->DropUniqueConstraint(this->label1, properties, {}).GetValue(), UniqueConstraints::DeletionStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED); // Remove one property from the set. @@ -697,29 +747,32 @@ TEST_F(ConstraintsTest, UniqueConstraintsPropertySetSize) { { // Creating a constraint for 32 properties should succeed. - auto res = storage.CreateUniqueConstraint(label1, properties); + auto res = this->storage->CreateUniqueConstraint(this->label1, properties, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } - EXPECT_THAT(storage.ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(label1, properties))); + EXPECT_THAT(this->storage->ListAllConstraints().unique, + UnorderedElementsAre(std::make_pair(this->label1, properties))); // Removing a constraint with 32 properties should succeed. - ASSERT_EQ(storage.DropUniqueConstraint(label1, properties).GetValue(), UniqueConstraints::DeletionStatus::SUCCESS); - ASSERT_TRUE(storage.ListAllConstraints().unique.empty()); + ASSERT_EQ(this->storage->DropUniqueConstraint(this->label1, properties, {}).GetValue(), + UniqueConstraints::DeletionStatus::SUCCESS); + ASSERT_TRUE(this->storage->ListAllConstraints().unique.empty()); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(ConstraintsTest, UniqueConstraintsMultipleProperties) { +/// TODO: andi consistency problems +TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { // An attempt to create an existing unique constraint. - auto res = storage.CreateUniqueConstraint(label1, {prop2, prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop2, this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::ALREADY_EXISTS); } @@ -727,151 +780,154 @@ TEST_F(ConstraintsTest, UniqueConstraintsMultipleProperties) { Gid gid1; Gid gid2; { - auto acc = storage.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex1.SetProperty(prop2, PropertyValue(2))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop2, PropertyValue(2))); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.SetProperty(prop2, PropertyValue(3))); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop2, PropertyValue(3))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } // Try to change property of the second vertex so it becomes the same as the - // first vertex. It should fail. + // first vertex-> It should fail. { - auto acc = storage.Access(); - auto vertex2 = acc.FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue(2))); - auto res = acc.Commit(); + auto acc = this->storage->Access(); + auto vertex2 = acc->FindVertex(gid2, View::OLD); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop2, PropertyValue(2))); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set<PropertyId>{prop1, prop2}})); + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, + std::set<PropertyId>{this->prop1, this->prop2}})); } // Then change the second property of both vertex to null. Property values of // both vertices should now be equal. However, this operation should succeed // since null value is treated as non-existing property. { - auto acc = storage.Access(); - auto vertex1 = acc.FindVertex(gid1, View::OLD); - auto vertex2 = acc.FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(vertex1->SetProperty(prop2, PropertyValue())); - ASSERT_NO_ERROR(vertex2->SetProperty(prop2, PropertyValue())); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex1 = acc->FindVertex(gid1, View::OLD); + auto vertex2 = acc->FindVertex(gid2, View::OLD); + ASSERT_NO_ERROR(vertex1->SetProperty(this->prop2, PropertyValue())); + ASSERT_NO_ERROR(vertex2->SetProperty(this->prop2, PropertyValue())); + ASSERT_NO_ERROR(acc->Commit()); } } -TEST_F(ConstraintsTest, UniqueConstraintsInsertAbortInsert) { +/// TODO: andi Test passes when ran alone but fails when all tests are run +TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertAbortInsert) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - acc.Abort(); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + acc->Abort(); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } } -TEST_F(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } Gid gid; { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); - ASSERT_NO_ERROR(acc.DeleteVertex(&*vertex)); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->FindVertex(gid, View::OLD); + ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex)); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(acc->Commit()); } } -TEST_F(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } Gid gid; { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); - ASSERT_NO_ERROR(acc.DeleteVertex(&*vertex)); - acc.Abort(); + auto acc = this->storage->Access(); + auto vertex = acc->FindVertex(gid, View::OLD); + ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex)); + acc->Abort(); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2))); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); - auto res = acc.Commit(); + auto res = acc->Commit(); ASSERT_TRUE(res.HasError()); - EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set{prop1, prop2}})); + EXPECT_EQ( + std::get<ConstraintViolation>(res.GetError()), + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set{this->prop1, this->prop2}})); } } -TEST_F(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } @@ -879,101 +935,140 @@ TEST_F(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) { Gid gid1; Gid gid2; { - auto acc = storage.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = this->storage->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); gid2 = vertex2.Gid(); - ASSERT_NO_ERROR(vertex1.AddLabel(label1)); - ASSERT_NO_ERROR(vertex2.AddLabel(label1)); - ASSERT_NO_ERROR(vertex1.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex2.SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop1, PropertyValue(2))); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc1 = storage.Access(); - auto acc2 = storage.Access(); - auto vertex1 = acc1.FindVertex(gid1, View::OLD); - auto vertex2 = acc2.FindVertex(gid2, View::OLD); + auto acc1 = this->storage->Access(); + auto acc2 = this->storage->Access(); + auto vertex1 = acc1->FindVertex(gid1, View::OLD); + auto vertex2 = acc2->FindVertex(gid2, View::OLD); - ASSERT_NO_ERROR(acc2.DeleteVertex(&*vertex2)); - ASSERT_NO_ERROR(vertex1->SetProperty(prop1, PropertyValue(2))); + ASSERT_NO_ERROR(acc2->DeleteVertex(&*vertex2)); + ASSERT_NO_ERROR(vertex1->SetProperty(this->prop1, PropertyValue(2))); - auto res = acc1.Commit(); + auto res = acc1->Commit(); ASSERT_TRUE(res.HasError()); EXPECT_EQ(std::get<ConstraintViolation>(res.GetError()), - (ConstraintViolation{ConstraintViolation::Type::UNIQUE, label1, std::set{prop1}})); + (ConstraintViolation{ConstraintViolation::Type::UNIQUE, this->label1, std::set{this->prop1}})); - ASSERT_NO_ERROR(acc2.Commit()); + ASSERT_NO_ERROR(acc2->Commit()); } } -TEST_F(ConstraintsTest, UniqueConstraintsInsertDropInsert) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertDropInsert) { { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(acc->Commit()); } - ASSERT_EQ(storage.DropUniqueConstraint(label1, {prop2, prop1}).GetValue(), + ASSERT_EQ(this->storage->DropUniqueConstraint(this->label1, {this->prop2, this->prop1}, {}).GetValue(), UniqueConstraints::DeletionStatus::SUCCESS); { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } } -TEST_F(ConstraintsTest, UniqueConstraintsComparePropertyValues) { +TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) { // Purpose of this test is to make sure that extracted property values // are correctly compared. { - auto res = storage.CreateUniqueConstraint(label1, {prop1, prop2}); + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1, this->prop2}, {}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(2))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(1))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(1))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(1))); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(2))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - auto vertex = acc.CreateVertex(); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop2, PropertyValue(0))); - ASSERT_NO_ERROR(vertex.SetProperty(prop1, PropertyValue(3))); - ASSERT_NO_ERROR(acc.Commit()); + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(0))); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(3))); + ASSERT_NO_ERROR(acc->Commit()); + } +} + +TYPED_TEST(ConstraintsTest, UniqueConstraintsClearOldData) { + // Purpose of this test is to make sure that extracted property values + // are correctly compared. + + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + auto *disk_constraints = + static_cast<memgraph::storage::DiskUniqueConstraints *>(this->storage->constraints_.unique_constraints_.get()); + auto *tx_db = disk_constraints->GetRocksDBStorage()->db_; + + { + auto res = this->storage->CreateUniqueConstraint(this->label1, {this->prop1}, {}); + ASSERT_TRUE(res.HasValue()); + ASSERT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); + } + + auto acc = this->storage->Access(); + auto vertex = acc->CreateVertex(); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); + ASSERT_NO_ERROR(acc->Commit()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = this->storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex2.SetProperty(this->prop1, memgraph::storage::PropertyValue(2)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc3 = this->storage->Access(std::nullopt); + auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex3.SetProperty(this->prop1, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc3->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); } } diff --git a/tests/unit/storage_v2_disk.cpp b/tests/unit/storage_v2_disk.cpp new file mode 100644 index 000000000..eefc45af9 --- /dev/null +++ b/tests/unit/storage_v2_disk.cpp @@ -0,0 +1,29 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <gtest/gtest.h> + +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" +#include "utils/file.hpp" + +class DiskStorageTest : public ::testing::TestWithParam<bool> {}; + +TEST_F(DiskStorageTest, CreateDiskStorageInDataDirectory) { + const std::string testSuite = "storage_v2_disk"; + + memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); + auto storage = std::make_unique<memgraph::storage::DiskStorage>(config); + ASSERT_TRUE(memgraph::utils::DirExists(config.disk.main_storage_directory)); + + disk_test_utils::RemoveRocksDbDirs(testSuite); +} diff --git a/tests/unit/storage_v2_durability.cpp b/tests/unit/storage_v2_durability_inmemory.cpp similarity index 70% rename from tests/unit/storage_v2_durability.cpp rename to tests/unit/storage_v2_durability_inmemory.cpp index 251f4a311..68e478045 100644 --- a/tests/unit/storage_v2_durability.cpp +++ b/tests/unit/storage_v2_durability_inmemory.cpp @@ -22,10 +22,14 @@ #include <iostream> #include <thread> +#include "storage/v2/durability/marker.hpp" #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/snapshot.hpp" #include "storage/v2/durability/version.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/durability/wal.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/inmemory/storage.hpp" +#include "storage/v2/vertex_accessor.hpp" #include "utils/file.hpp" #include "utils/logging.hpp" #include "utils/timer.hpp" @@ -80,15 +84,15 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { ASSERT_FALSE(store->CreateIndex(label_indexed, property_id).HasError()); // Create existence constraint. - ASSERT_FALSE(store->CreateExistenceConstraint(label_unindexed, property_id).HasError()); + ASSERT_FALSE(store->CreateExistenceConstraint(label_unindexed, property_id, {}).HasError()); // Create unique constraint. - ASSERT_FALSE(store->CreateUniqueConstraint(label_unindexed, {property_id, property_extra}).HasError()); + ASSERT_FALSE(store->CreateUniqueConstraint(label_unindexed, {property_id, property_extra}, {}).HasError()); // Create vertices. for (uint64_t i = 0; i < kNumBaseVertices; ++i) { auto acc = store->Access(); - auto vertex = acc.CreateVertex(); + auto vertex = acc->CreateVertex(); base_vertex_gids_[i] = vertex.Gid(); if (i < kNumBaseVertices / 2) { ASSERT_TRUE(vertex.AddLabel(label_indexed).HasValue()); @@ -99,15 +103,15 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { ASSERT_TRUE( vertex.SetProperty(property_id, memgraph::storage::PropertyValue(static_cast<int64_t>(i))).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edges. for (uint64_t i = 0; i < kNumBaseEdges; ++i) { auto acc = store->Access(); - auto vertex1 = acc.FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD); + auto vertex1 = acc->FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex1); - auto vertex2 = acc.FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD); + auto vertex2 = acc->FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex2); memgraph::storage::EdgeTypeId et; if (i < kNumBaseEdges / 2) { @@ -115,18 +119,19 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } else { et = et2; } - auto edge = acc.CreateEdge(&*vertex1, &*vertex2, et); - ASSERT_TRUE(edge.HasValue()); - base_edge_gids_[i] = edge->Gid(); + auto edgeRes = acc->CreateEdge(&*vertex1, &*vertex2, et); + ASSERT_TRUE(edgeRes.HasValue()); + auto edge = std::move(edgeRes.GetValue()); + base_edge_gids_[i] = edge.Gid(); if (properties_on_edges) { ASSERT_TRUE( - edge->SetProperty(property_id, memgraph::storage::PropertyValue(static_cast<int64_t>(i))).HasValue()); + edge.SetProperty(property_id, memgraph::storage::PropertyValue(static_cast<int64_t>(i))).HasValue()); } else { - auto ret = edge->SetProperty(property_id, memgraph::storage::PropertyValue(static_cast<int64_t>(i))); + auto ret = edge.SetProperty(property_id, memgraph::storage::PropertyValue(static_cast<int64_t>(i))); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -144,18 +149,18 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { ASSERT_FALSE(store->CreateIndex(label_indexed, property_count).HasError()); // Create existence constraint. - ASSERT_FALSE(store->CreateExistenceConstraint(label_unused, property_count).HasError()); + ASSERT_FALSE(store->CreateExistenceConstraint(label_unused, property_count, {}).HasError()); // Create unique constraint. - ASSERT_FALSE(store->CreateUniqueConstraint(label_unused, {property_count}).HasError()); + ASSERT_FALSE(store->CreateUniqueConstraint(label_unused, {property_count}, {}).HasError()); // Storage accessor. - std::optional<memgraph::storage::Storage::Accessor> acc; - if (single_transaction) acc.emplace(store->Access()); + std::unique_ptr<memgraph::storage::Storage::Accessor> acc; + if (single_transaction) acc = store->Access(); // Create vertices. for (uint64_t i = 0; i < kNumExtendedVertices; ++i) { - if (!single_transaction) acc.emplace(store->Access()); + if (!single_transaction) acc = store->Access(); auto vertex = acc->CreateVertex(); extended_vertex_gids_[i] = vertex.Gid(); if (i < kNumExtendedVertices / 2) { @@ -169,7 +174,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Create edges. for (uint64_t i = 0; i < kNumExtendedEdges; ++i) { - if (!single_transaction) acc.emplace(store->Access()); + if (!single_transaction) acc = store->Access(); auto vertex1 = acc->FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices], memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -182,9 +187,10 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } else { et = et4; } - auto edge = acc->CreateEdge(&*vertex1, &*vertex2, et); - ASSERT_TRUE(edge.HasValue()); - extended_edge_gids_[i] = edge->Gid(); + auto edgeRes = acc->CreateEdge(&*vertex1, &*vertex2, et); + ASSERT_TRUE(edgeRes.HasValue()); + auto edge = std::move(edgeRes.GetValue()); + extended_edge_gids_[i] = edge.Gid(); if (!single_transaction) ASSERT_FALSE(acc->Commit().HasError()); } @@ -281,7 +287,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { if (have_base_dataset) { // Verify vertices. for (uint64_t i = 0; i < kNumBaseVertices; ++i) { - auto vertex = acc.FindVertex(base_vertex_gids_[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(base_vertex_gids_[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto labels = vertex->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); @@ -302,17 +308,17 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify edges. for (uint64_t i = 0; i < kNumBaseEdges; ++i) { - auto find_edge = [&](const auto &edges) -> std::optional<memgraph::storage::EdgeAccessor> { - for (const auto &edge : edges) { + auto find_edge = [&](auto &edges) -> std::optional<memgraph::storage::EdgeAccessor> { + for (auto &edge : edges) { if (edge.Gid() == base_edge_gids_[i]) { return edge; } } - return std::nullopt; + return {}; }; { - auto vertex1 = acc.FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD); + auto vertex1 = acc->FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex1); auto out_edges = vertex1->OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); @@ -334,7 +340,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } { - auto vertex2 = acc.FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD); + auto vertex2 = acc->FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex2); auto in_edges = vertex2->InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); @@ -360,7 +366,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(kNumBaseVertices / 2); - for (auto vertex : acc.Vertices(base_label_unindexed, memgraph::storage::View::OLD)) { + for (auto vertex : acc->Vertices(base_label_unindexed, memgraph::storage::View::OLD)) { vertices.push_back(vertex); } ASSERT_EQ(vertices.size(), kNumBaseVertices / 2); @@ -374,7 +380,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(kNumBaseVertices / 3); - for (auto vertex : acc.Vertices(base_label_indexed, property_id, memgraph::storage::View::OLD)) { + for (auto vertex : acc->Vertices(base_label_indexed, property_id, memgraph::storage::View::OLD)) { vertices.push_back(vertex); } ASSERT_EQ(vertices.size(), kNumBaseVertices / 3); @@ -386,7 +392,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } else { // Verify vertices. for (uint64_t i = 0; i < kNumBaseVertices; ++i) { - auto vertex = acc.FindVertex(base_vertex_gids_[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(base_vertex_gids_[i], memgraph::storage::View::OLD); ASSERT_FALSE(vertex); } @@ -394,7 +400,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify label indices. { uint64_t count = 0; - auto iterable = acc.Vertices(base_label_unindexed, memgraph::storage::View::OLD); + auto iterable = acc->Vertices(base_label_unindexed, memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -404,7 +410,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify label+property index. { uint64_t count = 0; - auto iterable = acc.Vertices(base_label_indexed, property_id, memgraph::storage::View::OLD); + auto iterable = acc->Vertices(base_label_indexed, property_id, memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -417,7 +423,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { if (have_extended_dataset) { // Verify vertices. for (uint64_t i = 0; i < kNumExtendedVertices; ++i) { - auto vertex = acc.FindVertex(extended_vertex_gids_[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(extended_vertex_gids_[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto labels = vertex->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); @@ -436,18 +442,18 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify edges. for (uint64_t i = 0; i < kNumExtendedEdges; ++i) { - auto find_edge = [&](const auto &edges) -> std::optional<memgraph::storage::EdgeAccessor> { - for (const auto &edge : edges) { + auto find_edge = [&](auto &edges) -> std::optional<memgraph::storage::EdgeAccessor> { + for (auto &edge : edges) { if (edge.Gid() == extended_edge_gids_[i]) { return edge; } } - return std::nullopt; + return {}; }; { auto vertex1 = - acc.FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices], memgraph::storage::View::OLD); + acc->FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex1); auto out_edges = vertex1->OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); @@ -465,7 +471,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { auto vertex2 = - acc.FindVertex(extended_vertex_gids_[(i / 6) % kNumExtendedVertices], memgraph::storage::View::OLD); + acc->FindVertex(extended_vertex_gids_[(i / 6) % kNumExtendedVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex2); auto in_edges = vertex2->InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); @@ -486,8 +492,8 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(kNumExtendedVertices / 2); - for (auto vertex : acc.Vertices(extended_label_unused, memgraph::storage::View::OLD)) { - vertices.push_back(vertex); + for (auto vertex : acc->Vertices(extended_label_unused, memgraph::storage::View::OLD)) { + vertices.emplace_back(vertex); } ASSERT_EQ(vertices.size(), 0); } @@ -496,8 +502,8 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(kNumExtendedVertices / 3); - for (auto vertex : acc.Vertices(extended_label_indexed, property_count, memgraph::storage::View::OLD)) { - vertices.push_back(vertex); + for (auto vertex : acc->Vertices(extended_label_indexed, property_count, memgraph::storage::View::OLD)) { + vertices.emplace_back(vertex); } ASSERT_EQ(vertices.size(), kNumExtendedVertices / 3); std::sort(vertices.begin(), vertices.end(), [](const auto &a, const auto &b) { return a.Gid() < b.Gid(); }); @@ -508,7 +514,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { } else { // Verify vertices. for (uint64_t i = 0; i < kNumExtendedVertices; ++i) { - auto vertex = acc.FindVertex(extended_vertex_gids_[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(extended_vertex_gids_[i], memgraph::storage::View::OLD); ASSERT_FALSE(vertex); } @@ -516,7 +522,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify label indices. { uint64_t count = 0; - auto iterable = acc.Vertices(extended_label_unused, memgraph::storage::View::OLD); + auto iterable = acc->Vertices(extended_label_unused, memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -526,7 +532,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Verify label+property index. { uint64_t count = 0; - auto iterable = acc.Vertices(extended_label_indexed, property_count, memgraph::storage::View::OLD); + auto iterable = acc->Vertices(extended_label_indexed, property_count, memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -663,13 +669,13 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, DurabilityTest, ::testing::Value TEST_P(DurabilityTest, SnapshotOnExit) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, GetParam()); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); - CreateExtendedDataset(&store); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), GetParam()); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); + CreateExtendedDataset(store.get()); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -678,18 +684,18 @@ TEST_P(DurabilityTest, SnapshotOnExit) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -697,12 +703,12 @@ TEST_P(DurabilityTest, SnapshotOnExit) { TEST_P(DurabilityTest, SnapshotPeriodic) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(2000)}}); - CreateBaseDataset(&store, GetParam()); + .snapshot_interval = std::chrono::milliseconds(2000)}})); + CreateBaseDataset(store.get(), GetParam()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); } @@ -712,18 +718,18 @@ TEST_P(DurabilityTest, SnapshotPeriodic) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -731,15 +737,15 @@ TEST_P(DurabilityTest, SnapshotPeriodic) { TEST_P(DurabilityTest, SnapshotFallback) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(3000)}}); - CreateBaseDataset(&store, GetParam()); + .snapshot_interval = std::chrono::milliseconds(3000)}})); + CreateBaseDataset(store.get(), GetParam()); std::this_thread::sleep_for(std::chrono::milliseconds(3500)); ASSERT_EQ(GetSnapshotsList().size(), 1); - CreateExtendedDataset(&store); + CreateExtendedDataset(store.get()); std::this_thread::sleep_for(std::chrono::milliseconds(3000)); } @@ -756,18 +762,18 @@ TEST_P(DurabilityTest, SnapshotFallback) { } // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -775,14 +781,14 @@ TEST_P(DurabilityTest, SnapshotFallback) { TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { // Create unrelated snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - auto acc = store.Access(); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -801,14 +807,14 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(2000)}}); - CreateBaseDataset(&store, GetParam()); + .snapshot_interval = std::chrono::milliseconds(2000)}})); + CreateBaseDataset(store.get(), GetParam()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); - CreateExtendedDataset(&store); + CreateExtendedDataset(store.get()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); } @@ -842,9 +848,9 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { // Recover snapshot. ASSERT_DEATH( { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); }, ""); } @@ -853,14 +859,14 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { TEST_P(DurabilityTest, SnapshotRetention) { // Create unrelated snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - auto acc = store.Access(); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_GE(GetSnapshotsList().size(), 1); @@ -870,15 +876,15 @@ TEST_P(DurabilityTest, SnapshotRetention) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, .snapshot_interval = std::chrono::milliseconds(2000), - .snapshot_retention_count = 3}}); + .snapshot_retention_count = 3}})); // Restore unrelated snapshots after the database has been started. RestoreBackups(); - CreateBaseDataset(&store, GetParam()); + CreateBaseDataset(store.get(), GetParam()); // Allow approximately 5 snapshots to be created. std::this_thread::sleep_for(std::chrono::milliseconds(10000)); } @@ -907,18 +913,18 @@ TEST_P(DurabilityTest, SnapshotRetention) { } // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -926,13 +932,13 @@ TEST_P(DurabilityTest, SnapshotRetention) { TEST_P(DurabilityTest, SnapshotMixedUUID) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, GetParam()); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); - CreateExtendedDataset(&store); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), GetParam()); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); + CreateExtendedDataset(store.get()); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -942,19 +948,19 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { // Recover snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); } // Create another snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, GetParam()); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), GetParam()); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -971,18 +977,18 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -990,14 +996,14 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { TEST_P(DurabilityTest, SnapshotBackup) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - auto acc = store.Access(); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -1007,11 +1013,11 @@ TEST_P(DurabilityTest, SnapshotBackup) { // Start storage without recovery. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::minutes(20)}}); + .snapshot_interval = std::chrono::minutes(20)}})); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1024,13 +1030,13 @@ TEST_P(DurabilityTest, SnapshotBackup) { TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, false); - VerifyDataset(&store, DatasetType::ONLY_BASE, false); - CreateExtendedDataset(&store); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, false); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), false); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, false); + CreateExtendedDataset(store.get()); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, false); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -1039,18 +1045,18 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, false); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, false); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1058,13 +1064,13 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, true); - VerifyDataset(&store, DatasetType::ONLY_BASE, true); - CreateExtendedDataset(&store); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, true); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), true); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, true); + CreateExtendedDataset(store.get()); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, true); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -1075,9 +1081,9 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE // Recover snapshot. ASSERT_DEATH( { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); }, ""); } @@ -1086,20 +1092,20 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, true); - VerifyDataset(&store, DatasetType::ONLY_BASE, true); - CreateExtendedDataset(&store); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, true); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), true); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, true); + CreateExtendedDataset(store.get()); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, true); // Remove properties from edges. { - auto acc = store.Access(); - for (auto vertex : acc.Vertices(memgraph::storage::View::OLD)) { + auto acc = store->Access(); + for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { auto in_edges = vertex.InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); - for (auto edge : *in_edges) { + for (auto &edge : *in_edges) { // TODO (mferencevic): Replace with `ClearProperties()` auto props = edge.Properties(memgraph::storage::View::NEW); ASSERT_TRUE(props.HasValue()); @@ -1109,7 +1115,7 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp } auto out_edges = vertex.InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); - for (auto edge : *out_edges) { + for (auto &edge : *out_edges) { // TODO (mferencevic): Replace with `ClearProperties()` auto props = edge.Properties(memgraph::storage::View::NEW); ASSERT_TRUE(props.HasValue()); @@ -1118,7 +1124,7 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp } } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1128,18 +1134,18 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, false); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, false); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1147,15 +1153,15 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp TEST_P(DurabilityTest, WalBasic) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); - CreateExtendedDataset(&store); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); + CreateExtendedDataset(store.get()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1164,18 +1170,18 @@ TEST_P(DurabilityTest, WalBasic) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1183,19 +1189,19 @@ TEST_P(DurabilityTest, WalBasic) { TEST_P(DurabilityTest, WalBackup) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1206,12 +1212,12 @@ TEST_P(DurabilityTest, WalBackup) { // Start storage without recovery. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20)}}); + .snapshot_interval = std::chrono::minutes(20)}})); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1224,14 +1230,14 @@ TEST_P(DurabilityTest, WalBackup) { TEST_P(DurabilityTest, WalAppendToExisting) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1241,23 +1247,23 @@ TEST_P(DurabilityTest, WalAppendToExisting) { // Recover WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); } // Recover WALs and create more WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateExtendedDataset(&store); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateExtendedDataset(store.get()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1266,18 +1272,18 @@ TEST_P(DurabilityTest, WalAppendToExisting) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1288,34 +1294,35 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); - auto v1 = acc.CreateVertex(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); + auto v1 = acc->CreateVertex(); gid_v1 = v1.Gid(); - auto v2 = acc.CreateVertex(); + auto v2 = acc->CreateVertex(); gid_v2 = v2.Gid(); - auto e1 = acc.CreateEdge(&v1, &v2, store.NameToEdgeType("e1")); - ASSERT_TRUE(e1.HasValue()); - gid_e1 = e1->Gid(); - ASSERT_TRUE(v1.AddLabel(store.NameToLabel("l11")).HasValue()); - ASSERT_TRUE(v1.AddLabel(store.NameToLabel("l12")).HasValue()); - ASSERT_TRUE(v1.AddLabel(store.NameToLabel("l13")).HasValue()); + auto e1Res = acc->CreateEdge(&v1, &v2, store->NameToEdgeType("e1")); + ASSERT_TRUE(e1Res.HasValue()); + auto e1 = std::move(e1Res.GetValue()); + gid_e1 = e1.Gid(); + ASSERT_TRUE(v1.AddLabel(store->NameToLabel("l11")).HasValue()); + ASSERT_TRUE(v1.AddLabel(store->NameToLabel("l12")).HasValue()); + ASSERT_TRUE(v1.AddLabel(store->NameToLabel("l13")).HasValue()); if (GetParam()) { ASSERT_TRUE( - e1->SetProperty(store.NameToProperty("test"), memgraph::storage::PropertyValue("nandare")).HasValue()); + e1.SetProperty(store->NameToProperty("test"), memgraph::storage::PropertyValue("nandare")).HasValue()); } - ASSERT_TRUE(v2.AddLabel(store.NameToLabel("l21")).HasValue()); - ASSERT_TRUE(v2.SetProperty(store.NameToProperty("hello"), memgraph::storage::PropertyValue("world")).HasValue()); - auto v3 = acc.CreateVertex(); + ASSERT_TRUE(v2.AddLabel(store->NameToLabel("l21")).HasValue()); + ASSERT_TRUE(v2.SetProperty(store->NameToProperty("hello"), memgraph::storage::PropertyValue("world")).HasValue()); + auto v3 = acc->CreateVertex(); gid_v3 = v3.Gid(); - ASSERT_TRUE(v3.SetProperty(store.NameToProperty("v3"), memgraph::storage::PropertyValue(42)).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(v3.SetProperty(store->NameToProperty("v3"), memgraph::storage::PropertyValue(42)).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1324,24 +1331,24 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); { - auto indices = store.ListAllIndices(); + auto indices = store->ListAllIndices(); ASSERT_EQ(indices.label.size(), 0); ASSERT_EQ(indices.label_property.size(), 0); - auto constraints = store.ListAllConstraints(); + auto constraints = store->ListAllConstraints(); ASSERT_EQ(constraints.existence.size(), 0); ASSERT_EQ(constraints.unique.size(), 0); - auto acc = store.Access(); + auto acc = store->Access(); { - auto v1 = acc.FindVertex(gid_v1, memgraph::storage::View::OLD); + auto v1 = acc->FindVertex(gid_v1, memgraph::storage::View::OLD); ASSERT_TRUE(v1); auto labels = v1->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); - ASSERT_THAT(*labels, - UnorderedElementsAre(store.NameToLabel("l11"), store.NameToLabel("l12"), store.NameToLabel("l13"))); + ASSERT_THAT(*labels, UnorderedElementsAre(store->NameToLabel("l11"), store->NameToLabel("l12"), + store->NameToLabel("l13"))); auto props = v1->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(props.HasValue()); ASSERT_EQ(props->size(), 0); @@ -1356,21 +1363,21 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { auto edge_props = edge.Properties(memgraph::storage::View::OLD); ASSERT_TRUE(edge_props.HasValue()); if (GetParam()) { - ASSERT_THAT(*edge_props, UnorderedElementsAre(std::make_pair(store.NameToProperty("test"), + ASSERT_THAT(*edge_props, UnorderedElementsAre(std::make_pair(store->NameToProperty("test"), memgraph::storage::PropertyValue("nandare")))); } else { ASSERT_EQ(edge_props->size(), 0); } } { - auto v2 = acc.FindVertex(gid_v2, memgraph::storage::View::OLD); + auto v2 = acc->FindVertex(gid_v2, memgraph::storage::View::OLD); ASSERT_TRUE(v2); auto labels = v2->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); - ASSERT_THAT(*labels, UnorderedElementsAre(store.NameToLabel("l21"))); + ASSERT_THAT(*labels, UnorderedElementsAre(store->NameToLabel("l21"))); auto props = v2->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(props.HasValue()); - ASSERT_THAT(*props, UnorderedElementsAre(std::make_pair(store.NameToProperty("hello"), + ASSERT_THAT(*props, UnorderedElementsAre(std::make_pair(store->NameToProperty("hello"), memgraph::storage::PropertyValue("world")))); auto in_edges = v2->InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); @@ -1380,7 +1387,7 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { auto edge_props = edge.Properties(memgraph::storage::View::OLD); ASSERT_TRUE(edge_props.HasValue()); if (GetParam()) { - ASSERT_THAT(*edge_props, UnorderedElementsAre(std::make_pair(store.NameToProperty("test"), + ASSERT_THAT(*edge_props, UnorderedElementsAre(std::make_pair(store->NameToProperty("test"), memgraph::storage::PropertyValue("nandare")))); } else { ASSERT_EQ(edge_props->size(), 0); @@ -1390,7 +1397,7 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { ASSERT_EQ(out_edges->size(), 0); } { - auto v3 = acc.FindVertex(gid_v3, memgraph::storage::View::OLD); + auto v3 = acc->FindVertex(gid_v3, memgraph::storage::View::OLD); ASSERT_TRUE(v3); auto labels = v3->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); @@ -1398,7 +1405,7 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { auto props = v3->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(props.HasValue()); ASSERT_THAT(*props, UnorderedElementsAre( - std::make_pair(store.NameToProperty("v3"), memgraph::storage::PropertyValue(42)))); + std::make_pair(store->NameToProperty("v3"), memgraph::storage::PropertyValue(42)))); auto in_edges = v3->InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); ASSERT_EQ(in_edges->size(), 0); @@ -1410,11 +1417,11 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1422,35 +1429,35 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); - CreateExtendedDataset(&store); - auto indices = store.ListAllIndices(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); + CreateExtendedDataset(store.get()); + auto indices = store->ListAllIndices(); for (const auto &index : indices.label) { - ASSERT_FALSE(store.DropIndex(index).HasError()); + ASSERT_FALSE(store->DropIndex(index).HasError()); } for (const auto &index : indices.label_property) { - ASSERT_FALSE(store.DropIndex(index.first, index.second).HasError()); + ASSERT_FALSE(store->DropIndex(index.first, index.second).HasError()); } - auto constraints = store.ListAllConstraints(); + auto constraints = store->ListAllConstraints(); for (const auto &constraint : constraints.existence) { - ASSERT_FALSE(store.DropExistenceConstraint(constraint.first, constraint.second).HasError()); + ASSERT_FALSE(store->DropExistenceConstraint(constraint.first, constraint.second, {}).HasError()); } for (const auto &constraint : constraints.unique) { - ASSERT_EQ(store.DropUniqueConstraint(constraint.first, constraint.second).GetValue(), + ASSERT_EQ(store->DropUniqueConstraint(constraint.first, constraint.second, {}).GetValue(), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); } - auto acc = store.Access(); - for (auto vertex : acc.Vertices(memgraph::storage::View::OLD)) { - ASSERT_TRUE(acc.DetachDeleteVertex(&vertex).HasValue()); + auto acc = store->Access(); + for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { + ASSERT_TRUE(acc->DetachDeleteVertex(&vertex).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1459,19 +1466,19 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); { - auto indices = store.ListAllIndices(); + auto indices = store->ListAllIndices(); ASSERT_EQ(indices.label.size(), 0); ASSERT_EQ(indices.label_property.size(), 0); - auto constraints = store.ListAllConstraints(); + auto constraints = store->ListAllConstraints(); ASSERT_EQ(constraints.existence.size(), 0); ASSERT_EQ(constraints.unique.size(), 0); - auto acc = store.Access(); + auto acc = store->Access(); uint64_t count = 0; - auto iterable = acc.Vertices(memgraph::storage::View::OLD); + auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -1480,11 +1487,11 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1495,7 +1502,7 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { // Create WAL. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, @@ -1503,37 +1510,37 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 100000, .wal_file_flush_every_n_tx = kFlushWalEvery, - }}); - auto acc1 = store.Access(); - auto acc2 = store.Access(); + }})); + auto acc1 = store->Access(); + auto acc2 = store->Access(); // Create vertex in transaction 2. { - auto vertex2 = acc2.CreateVertex(); + auto vertex2 = acc2->CreateVertex(); gid2 = vertex2.Gid(); - ASSERT_TRUE(vertex2.SetProperty(store.NameToProperty("id"), memgraph::storage::PropertyValue(2)).HasValue()); + ASSERT_TRUE(vertex2.SetProperty(store->NameToProperty("id"), memgraph::storage::PropertyValue(2)).HasValue()); } - auto acc3 = store.Access(); + auto acc3 = store->Access(); // Create vertex in transaction 3. { - auto vertex3 = acc3.CreateVertex(); + auto vertex3 = acc3->CreateVertex(); gid3 = vertex3.Gid(); - ASSERT_TRUE(vertex3.SetProperty(store.NameToProperty("id"), memgraph::storage::PropertyValue(3)).HasValue()); + ASSERT_TRUE(vertex3.SetProperty(store->NameToProperty("id"), memgraph::storage::PropertyValue(3)).HasValue()); } // Create vertex in transaction 1. { - auto vertex1 = acc1.CreateVertex(); + auto vertex1 = acc1->CreateVertex(); gid1 = vertex1.Gid(); - ASSERT_TRUE(vertex1.SetProperty(store.NameToProperty("id"), memgraph::storage::PropertyValue(1)).HasValue()); + ASSERT_TRUE(vertex1.SetProperty(store->NameToProperty("id"), memgraph::storage::PropertyValue(1)).HasValue()); } // Commit transaction 3, then 1, then 2. - ASSERT_FALSE(acc3.Commit().HasError()); - ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc3->Commit().HasError()); + ASSERT_FALSE(acc1->Commit().HasError()); + ASSERT_FALSE(acc2->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1590,13 +1597,13 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { } // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); { - auto acc = store.Access(); + auto acc = store->Access(); for (auto [gid, id] : std::vector<std::pair<memgraph::storage::Gid, int64_t>>{{gid1, 1}, {gid2, 2}, {gid3, 3}}) { - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto labels = vertex->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); @@ -1604,17 +1611,17 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { auto props = vertex->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(props.HasValue()); ASSERT_EQ(props->size(), 1); - ASSERT_EQ(props->at(store.NameToProperty("id")), memgraph::storage::PropertyValue(id)); + ASSERT_EQ(props->at(store->NameToProperty("id")), memgraph::storage::PropertyValue(id)); } } // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1622,26 +1629,26 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); - CreateExtendedDataset(&store); - auto label_indexed = store.NameToLabel("base_indexed"); - auto label_unindexed = store.NameToLabel("base_unindexed"); - auto acc = store.Access(); - for (auto vertex : acc.Vertices(memgraph::storage::View::OLD)) { + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); + CreateExtendedDataset(store.get()); + auto label_indexed = store->NameToLabel("base_indexed"); + auto label_unindexed = store->NameToLabel("base_unindexed"); + auto acc = store->Access(); + for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { auto has_indexed = vertex.HasLabel(label_indexed, memgraph::storage::View::OLD); ASSERT_TRUE(has_indexed.HasValue()); auto has_unindexed = vertex.HasLabel(label_unindexed, memgraph::storage::View::OLD); if (!*has_indexed && !*has_unindexed) continue; - ASSERT_TRUE(acc.DetachDeleteVertex(&vertex).HasValue()); + ASSERT_TRUE(acc->DetachDeleteVertex(&vertex).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1650,18 +1657,18 @@ TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_EXTENDED_WITH_BASE_INDICES_AND_CONSTRAINTS, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_EXTENDED_WITH_BASE_INDICES_AND_CONSTRAINTS, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1671,18 +1678,18 @@ TEST_P(DurabilityTest, WalDeathResilience) { if (pid == 0) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); // Create one million vertices. for (uint64_t i = 0; i < 1000000; ++i) { - auto acc = store.Access(); - acc.CreateVertex(); - MG_ASSERT(!acc.Commit().HasError(), "Couldn't commit transaction!"); + auto acc = store->Access(); + acc->CreateVertex(); + MG_ASSERT(!acc->Commit().HasError(), "Couldn't commit transaction!"); } } } else if (pid > 0) { @@ -1706,7 +1713,7 @@ TEST_P(DurabilityTest, WalDeathResilience) { const uint64_t kExtraItems = 1000; uint64_t count = 0; { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, @@ -1714,10 +1721,10 @@ TEST_P(DurabilityTest, WalDeathResilience) { .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_flush_every_n_tx = kFlushWalEvery, - }}); + }})); { - auto acc = store.Access(); - auto iterable = acc.Vertices(memgraph::storage::View::OLD); + auto acc = store->Access(); + auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -1725,11 +1732,11 @@ TEST_P(DurabilityTest, WalDeathResilience) { } { - auto acc = store.Access(); + auto acc = store->Access(); for (uint64_t i = 0; i < kExtraItems; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1739,13 +1746,13 @@ TEST_P(DurabilityTest, WalDeathResilience) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); { uint64_t current = 0; - auto acc = store.Access(); - auto iterable = acc.Vertices(memgraph::storage::View::OLD); + auto acc = store->Access(); + auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++current; } @@ -1754,11 +1761,11 @@ TEST_P(DurabilityTest, WalDeathResilience) { // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1766,19 +1773,19 @@ TEST_P(DurabilityTest, WalDeathResilience) { TEST_P(DurabilityTest, WalMissingSecond) { // Create unrelated WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1790,30 +1797,30 @@ TEST_P(DurabilityTest, WalMissingSecond) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); const uint64_t kNumVertices = 1000; std::vector<memgraph::storage::Gid> gids; gids.reserve(kNumVertices); for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids.push_back(vertex.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(vertex->SetProperty(store.NameToProperty("nandare"), memgraph::storage::PropertyValue("haihaihai!")) + ASSERT_TRUE(vertex->SetProperty(store->NameToProperty("nandare"), memgraph::storage::PropertyValue("haihaihai!")) .HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1842,9 +1849,9 @@ TEST_P(DurabilityTest, WalMissingSecond) { // Recover WALs. ASSERT_DEATH( { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); }, ""); } @@ -1853,19 +1860,19 @@ TEST_P(DurabilityTest, WalMissingSecond) { TEST_P(DurabilityTest, WalCorruptSecond) { // Create unrelated WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1877,30 +1884,30 @@ TEST_P(DurabilityTest, WalCorruptSecond) { // Create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); const uint64_t kNumVertices = 1000; std::vector<memgraph::storage::Gid> gids; gids.reserve(kNumVertices); for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gids.push_back(vertex.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gids[i], memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); - ASSERT_TRUE(vertex->SetProperty(store.NameToProperty("nandare"), memgraph::storage::PropertyValue("haihaihai!")) + ASSERT_TRUE(vertex->SetProperty(store->NameToProperty("nandare"), memgraph::storage::PropertyValue("haihaihai!")) .HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1928,9 +1935,9 @@ TEST_P(DurabilityTest, WalCorruptSecond) { // Recover WALs. ASSERT_DEATH( { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); }, ""); } @@ -1939,16 +1946,16 @@ TEST_P(DurabilityTest, WalCorruptSecond) { TEST_P(DurabilityTest, WalCorruptLastTransaction) { // Create WALs { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); - CreateExtendedDataset(&store, /* single_transaction = */ true); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); + CreateExtendedDataset(store.get(), /* single_transaction = */ true); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -1965,20 +1972,20 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) { } // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); // The extended dataset shouldn't be recovered because its WAL transaction was // corrupt. - VerifyDataset(&store, DatasetType::ONLY_BASE_WITH_EXTENDED_INDICES_AND_CONSTRAINTS, GetParam()); + VerifyDataset(store.get(), DatasetType::ONLY_BASE_WITH_EXTENDED_INDICES_AND_CONSTRAINTS, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -1986,44 +1993,47 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) { TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { // Create WALs { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); - ASSERT_TRUE(vertex1.AddLabel(acc.NameToLabel("nandare")).HasValue()); - ASSERT_TRUE(vertex2.SetProperty(acc.NameToProperty("haihai"), memgraph::storage::PropertyValue(42)).HasValue()); - ASSERT_TRUE(vertex1.RemoveLabel(acc.NameToLabel("nandare")).HasValue()); - auto edge1 = acc.CreateEdge(&vertex1, &vertex2, acc.NameToEdgeType("et1")); - ASSERT_TRUE(edge1.HasValue()); - ASSERT_TRUE(vertex2.SetProperty(acc.NameToProperty("haihai"), memgraph::storage::PropertyValue()).HasValue()); - auto vertex3 = acc.CreateVertex(); - auto edge2 = acc.CreateEdge(&vertex3, &vertex3, acc.NameToEdgeType("et2")); - ASSERT_TRUE(edge2.HasValue()); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); + ASSERT_TRUE(vertex1.AddLabel(acc->NameToLabel("nandare")).HasValue()); + ASSERT_TRUE(vertex2.SetProperty(acc->NameToProperty("haihai"), memgraph::storage::PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex1.RemoveLabel(acc->NameToLabel("nandare")).HasValue()); + auto edge1Res = acc->CreateEdge(&vertex1, &vertex2, acc->NameToEdgeType("et1")); + ASSERT_TRUE(edge1Res.HasValue()); + auto edge1 = std::move(edge1Res.GetValue()); + + ASSERT_TRUE(vertex2.SetProperty(acc->NameToProperty("haihai"), memgraph::storage::PropertyValue()).HasValue()); + auto vertex3 = acc->CreateVertex(); + auto edge2Res = acc->CreateEdge(&vertex3, &vertex3, acc->NameToEdgeType("et2")); + ASSERT_TRUE(edge2Res.HasValue()); + auto edge2 = std::move(edge2Res.GetValue()); if (GetParam()) { - ASSERT_TRUE(edge2->SetProperty(acc.NameToProperty("meaning"), memgraph::storage::PropertyValue(true)).HasValue()); + ASSERT_TRUE(edge2.SetProperty(acc->NameToProperty("meaning"), memgraph::storage::PropertyValue(true)).HasValue()); ASSERT_TRUE( - edge1->SetProperty(acc.NameToProperty("hello"), memgraph::storage::PropertyValue("world")).HasValue()); - ASSERT_TRUE(edge2->SetProperty(acc.NameToProperty("meaning"), memgraph::storage::PropertyValue()).HasValue()); + edge1.SetProperty(acc->NameToProperty("hello"), memgraph::storage::PropertyValue("world")).HasValue()); + ASSERT_TRUE(edge2.SetProperty(acc->NameToProperty("meaning"), memgraph::storage::PropertyValue()).HasValue()); } - ASSERT_TRUE(vertex3.AddLabel(acc.NameToLabel("test")).HasValue()); - ASSERT_TRUE(vertex3.SetProperty(acc.NameToProperty("nonono"), memgraph::storage::PropertyValue(-1)).HasValue()); - ASSERT_TRUE(vertex3.SetProperty(acc.NameToProperty("nonono"), memgraph::storage::PropertyValue()).HasValue()); + ASSERT_TRUE(vertex3.AddLabel(acc->NameToLabel("test")).HasValue()); + ASSERT_TRUE(vertex3.SetProperty(acc->NameToProperty("nonono"), memgraph::storage::PropertyValue(-1)).HasValue()); + ASSERT_TRUE(vertex3.SetProperty(acc->NameToProperty("nonono"), memgraph::storage::PropertyValue()).HasValue()); if (GetParam()) { - ASSERT_TRUE(edge1->SetProperty(acc.NameToProperty("hello"), memgraph::storage::PropertyValue()).HasValue()); + ASSERT_TRUE(edge1.SetProperty(acc->NameToProperty("hello"), memgraph::storage::PropertyValue()).HasValue()); } - ASSERT_TRUE(vertex3.RemoveLabel(acc.NameToLabel("test")).HasValue()); - ASSERT_TRUE(acc.DetachDeleteVertex(&vertex1).HasValue()); - ASSERT_TRUE(acc.DeleteEdge(&*edge2).HasValue()); - ASSERT_TRUE(acc.DeleteVertex(&vertex2).HasValue()); - ASSERT_TRUE(acc.DeleteVertex(&vertex3).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(vertex3.RemoveLabel(acc->NameToLabel("test")).HasValue()); + ASSERT_TRUE(acc->DetachDeleteVertex(&vertex1).HasValue()); + ASSERT_TRUE(acc->DeleteEdge(&edge2).HasValue()); + ASSERT_TRUE(acc->DeleteVertex(&vertex2).HasValue()); + ASSERT_TRUE(acc->DeleteVertex(&vertex3).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -2032,13 +2042,13 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); { - auto acc = store.Access(); + auto acc = store->Access(); uint64_t count = 0; - auto iterable = acc.Vertices(memgraph::storage::View::OLD); + auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; } @@ -2047,11 +2057,11 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -2059,16 +2069,16 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { TEST_P(DurabilityTest, WalAndSnapshot) { // Create snapshot and WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::milliseconds(2000), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateBaseDataset(&store, GetParam()); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateBaseDataset(store.get(), GetParam()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); - CreateExtendedDataset(&store); + CreateExtendedDataset(store.get()); } ASSERT_GE(GetSnapshotsList().size(), 1); @@ -2077,18 +2087,18 @@ TEST_P(DurabilityTest, WalAndSnapshot) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -2096,10 +2106,10 @@ TEST_P(DurabilityTest, WalAndSnapshot) { TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, GetParam()); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -2109,23 +2119,23 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { // Recover snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); } // Recover snapshot and create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateExtendedDataset(&store); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateExtendedDataset(store.get()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -2134,18 +2144,18 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -2153,10 +2163,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Create snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}); - CreateBaseDataset(&store, GetParam()); + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}})); + CreateBaseDataset(store.get(), GetParam()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -2166,23 +2176,23 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Recover snapshot. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::ONLY_BASE, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::ONLY_BASE, GetParam()); } // Recover snapshot and create WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - CreateExtendedDataset(&store); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + CreateExtendedDataset(store.get()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -2193,22 +2203,23 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Recover snapshot and WALs and create more WALs. memgraph::storage::Gid vertex_gid; { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); vertex_gid = vertex.Gid(); if (GetParam()) { - ASSERT_TRUE(vertex.SetProperty(store.NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); + ASSERT_TRUE( + vertex.SetProperty(store->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 1); @@ -2217,14 +2228,14 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam(), + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam(), /* verify_info = */ false); { - auto acc = store.Access(); - auto vertex = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto labels = vertex->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); @@ -2233,7 +2244,7 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { ASSERT_TRUE(props.HasValue()); if (GetParam()) { ASSERT_THAT(*props, UnorderedElementsAre( - std::make_pair(store.NameToProperty("meaning"), memgraph::storage::PropertyValue(42)))); + std::make_pair(store->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)))); } else { ASSERT_EQ(props->size(), 0); } @@ -2241,11 +2252,11 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -2253,19 +2264,19 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Create unrelated WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::minutes(20), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}); - auto acc = store.Access(); + .wal_file_flush_every_n_tx = kFlushWalEvery}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } ASSERT_EQ(GetSnapshotsList().size(), 0); @@ -2279,22 +2290,22 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Create snapshot and WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_interval = std::chrono::seconds(2), .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = 1}}); + .wal_file_flush_every_n_tx = 1}})); // Restore unrelated snapshots after the database has been started. RestoreBackups(); memgraph::utils::Timer timer; // Allow at least 6 snapshots to be created. while (timer.Elapsed().count() < 13.0) { - auto acc = store.Access(); - acc.CreateVertex(); - ASSERT_FALSE(acc.Commit().HasError()); + auto acc = store->Access(); + acc->CreateVertex(); + ASSERT_FALSE(acc->Commit().HasError()); ++items_created; } } @@ -2312,12 +2323,12 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Recover and verify data. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - auto acc = store.Access(); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + auto acc = store->Access(); for (uint64_t j = 0; j < items_created; ++j) { - auto vertex = acc.FindVertex(memgraph::storage::Gid::FromUint(j), memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(memgraph::storage::Gid::FromUint(j), memgraph::storage::View::OLD); ASSERT_TRUE(vertex); } } @@ -2330,9 +2341,9 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // shouldn't be possible because the initial WALs are already deleted. ASSERT_DEATH( { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); }, ""); } @@ -2341,17 +2352,17 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { // Create unrelated snapshot and WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::seconds(2)}}); - auto acc = store.Access(); + .snapshot_interval = std::chrono::seconds(2)}})); + auto acc = store->Access(); for (uint64_t i = 0; i < 1000; ++i) { - acc.CreateVertex(); + acc->CreateVertex(); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); } @@ -2362,15 +2373,15 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { // Create snapshot and WALs. { - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, .durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::seconds(2)}}); - CreateBaseDataset(&store, GetParam()); + .snapshot_interval = std::chrono::seconds(2)}})); + CreateBaseDataset(store.get(), GetParam()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); - CreateExtendedDataset(&store); + CreateExtendedDataset(store.get()); std::this_thread::sleep_for(std::chrono::milliseconds(2500)); } @@ -2388,17 +2399,17 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Storage store( + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::InMemoryStorage( {.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}); - VerifyDataset(&store, DatasetType::BASE_WITH_EXTENDED, GetParam()); + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}})); + VerifyDataset(store.get(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, store.NameToEdgeType("et")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, store->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } diff --git a/tests/unit/storage_v2_edge.cpp b/tests/unit/storage_v2_edge_inmemory.cpp similarity index 86% rename from tests/unit/storage_v2_edge.cpp rename to tests/unit/storage_v2_edge_inmemory.cpp index 1d62fe35a..966ac8790 100644 --- a/tests/unit/storage_v2_edge.cpp +++ b/tests/unit/storage_v2_edge_inmemory.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,6 +14,7 @@ #include <limits> +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" using testing::UnorderedElementsAre; @@ -25,31 +26,32 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Valu // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -92,7 +94,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -110,18 +112,18 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -177,7 +179,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -209,37 +211,38 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_to = acc.CreateVertex(); - auto vertex_from = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); gid_from = vertex_from.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -282,7 +285,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -294,18 +297,18 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -361,7 +364,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -381,32 +384,33 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex, &*vertex, et); + auto res = acc->CreateEdge(&*vertex, &*vertex, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -441,7 +445,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -453,16 +457,16 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters { @@ -510,7 +514,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -526,37 +530,38 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge, but abort the transaction { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -599,7 +604,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -611,14 +616,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -640,20 +645,20 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -696,7 +701,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -708,18 +713,18 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -775,7 +780,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -795,37 +800,38 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_to = acc.CreateVertex(); - auto vertex_from = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); gid_from = vertex_from.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge, but abort the transaction { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -868,7 +874,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -880,14 +886,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -909,20 +915,20 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -965,7 +971,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -977,18 +983,18 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -1044,7 +1050,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1064,32 +1070,33 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge, but abort the transaction { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex, &*vertex, et); + auto res = acc->CreateEdge(&*vertex, &*vertex, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -1124,7 +1131,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -1136,13 +1143,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); // Check edges without filters @@ -1155,18 +1162,18 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex, &*vertex, et); + auto res = acc->CreateEdge(&*vertex, &*vertex, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -1201,7 +1208,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -1213,16 +1220,16 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters { @@ -1270,7 +1277,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1290,37 +1297,38 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -1363,7 +1371,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -1375,18 +1383,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -1442,7 +1450,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1462,22 +1470,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -1517,7 +1525,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1529,14 +1537,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -1558,37 +1566,38 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_to = acc.CreateVertex(); - auto vertex_from = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -1631,7 +1640,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -1643,18 +1652,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -1710,7 +1719,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1730,22 +1739,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -1785,7 +1794,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1797,14 +1806,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -1826,32 +1835,33 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex, &*vertex, et); + auto res = acc->CreateEdge(&*vertex, &*vertex, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -1886,7 +1896,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -1898,16 +1908,16 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters { @@ -1955,7 +1965,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -1975,20 +1985,20 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -2020,7 +2030,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2032,13 +2042,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); // Check edges without filters @@ -2051,37 +2061,38 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -2124,7 +2135,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -2136,18 +2147,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -2203,7 +2214,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2223,22 +2234,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge, but abort the transaction { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -2278,7 +2289,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2290,18 +2301,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -2357,7 +2368,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2377,22 +2388,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -2432,7 +2443,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2444,14 +2455,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -2473,37 +2484,38 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); gid_to = vertex_to.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, et); + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -2546,7 +2558,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -2558,18 +2570,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -2625,7 +2637,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2645,22 +2657,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge, but abort the transaction { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -2700,7 +2712,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2712,18 +2724,18 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); @@ -2780,7 +2792,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2800,22 +2812,22 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -2855,7 +2867,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -2867,14 +2879,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); @@ -2896,32 +2908,33 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Create edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&*vertex, &*vertex, et); + auto res = acc->CreateEdge(&*vertex, &*vertex, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -2956,7 +2969,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); @@ -2968,16 +2981,16 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters { @@ -3025,7 +3038,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -3045,20 +3058,20 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge, but abort the transaction { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -3090,7 +3103,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -3102,16 +3115,16 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges without filters { @@ -3159,7 +3172,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(e.ToVertex(), *vertex); } - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -3179,20 +3192,20 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Delete the edge { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto res = acc.DeleteEdge(&edge); + auto res = acc->DeleteEdge(&edge); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); @@ -3224,7 +3237,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - auto other_et = acc.NameToEdgeType("other"); + auto other_et = acc->NameToEdgeType("other"); // Check edges with filters ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); @@ -3236,13 +3249,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check whether the edge exists { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid_vertex, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); // Check edges without filters @@ -3255,25 +3268,26 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&vertex_from, &vertex_to, et); + auto res = acc->CreateEdge(&vertex_from, &vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -3311,29 +3325,29 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { ASSERT_EQ(vertex_to.OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to.OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex_from); + auto ret = acc->DeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex_from); + auto ret = acc->DetachDeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -3375,14 +3389,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check dataset { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); ASSERT_TRUE(vertex_to); @@ -3400,46 +3414,47 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid_vertex1 = vertex1.Gid(); gid_vertex2 = vertex2.Gid(); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); - auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1); + auto res1 = acc->CreateEdge(&vertex1, &vertex2, et1); ASSERT_TRUE(res1.HasValue()); auto edge1 = res1.GetValue(); ASSERT_EQ(edge1.EdgeType(), et1); ASSERT_EQ(edge1.FromVertex(), vertex1); ASSERT_EQ(edge1.ToVertex(), vertex2); - auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2); + auto res2 = acc->CreateEdge(&vertex2, &vertex1, et2); ASSERT_TRUE(res2.HasValue()); auto edge2 = res2.GetValue(); ASSERT_EQ(edge2.EdgeType(), et2); ASSERT_EQ(edge2.FromVertex(), vertex2); ASSERT_EQ(edge2.ToVertex(), vertex1); - auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3); + auto res3 = acc->CreateEdge(&vertex1, &vertex1, et3); ASSERT_TRUE(res3.HasValue()); auto edge3 = res3.GetValue(); ASSERT_EQ(edge3.EdgeType(), et3); ASSERT_EQ(edge3.FromVertex(), vertex1); ASSERT_EQ(edge3.ToVertex(), vertex1); - auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4); + auto res4 = acc->CreateEdge(&vertex2, &vertex2, et4); ASSERT_TRUE(res4.HasValue()); auto edge4 = res4.GetValue(); ASSERT_EQ(edge4.EdgeType(), et4); @@ -3528,32 +3543,32 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); ASSERT_TRUE(vertex2); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex1); + auto ret = acc->DeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex1); + auto ret = acc->DetachDeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -3666,18 +3681,18 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { ASSERT_EQ(e.ToVertex(), *vertex2); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check dataset { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); ASSERT_TRUE(vertex2); - auto et4 = acc.NameToEdgeType("et4"); + auto et4 = acc->NameToEdgeType("et4"); // Check edges { @@ -3729,19 +3744,20 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store.Access(); - auto vertex_from = acc.CreateVertex(); - auto vertex_to = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); - auto res = acc.CreateEdge(&vertex_from, &vertex_to, et); + auto res = acc->CreateEdge(&vertex_from, &vertex_to, et); ASSERT_TRUE(res.HasValue()); auto edge = res.GetValue(); ASSERT_EQ(edge.EdgeType(), et); @@ -3779,29 +3795,29 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { ASSERT_EQ(vertex_to.OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to.OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex, but abort the transaction { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex_from); + auto ret = acc->DeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex_from); + auto ret = acc->DetachDeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -3843,18 +3859,18 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - acc.Abort(); + acc->Abort(); } // Check dataset { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Check edges ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); @@ -3884,29 +3900,29 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); ASSERT_TRUE(vertex_to); - auto et = acc.NameToEdgeType("et5"); + auto et = acc->NameToEdgeType("et5"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex_from); + auto ret = acc->DeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex_from); + auto ret = acc->DetachDeleteVertex(&*vertex_from); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -3948,14 +3964,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check dataset { - auto acc = store.Access(); - auto vertex_from = acc.FindVertex(gid_from, memgraph::storage::View::NEW); - auto vertex_to = acc.FindVertex(gid_to, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); ASSERT_TRUE(vertex_to); @@ -3973,46 +3989,47 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store.Access(); - auto vertex1 = acc.CreateVertex(); - auto vertex2 = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); gid_vertex1 = vertex1.Gid(); gid_vertex2 = vertex2.Gid(); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); - auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1); + auto res1 = acc->CreateEdge(&vertex1, &vertex2, et1); ASSERT_TRUE(res1.HasValue()); auto edge1 = res1.GetValue(); ASSERT_EQ(edge1.EdgeType(), et1); ASSERT_EQ(edge1.FromVertex(), vertex1); ASSERT_EQ(edge1.ToVertex(), vertex2); - auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2); + auto res2 = acc->CreateEdge(&vertex2, &vertex1, et2); ASSERT_TRUE(res2.HasValue()); auto edge2 = res2.GetValue(); ASSERT_EQ(edge2.EdgeType(), et2); ASSERT_EQ(edge2.FromVertex(), vertex2); ASSERT_EQ(edge2.ToVertex(), vertex1); - auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3); + auto res3 = acc->CreateEdge(&vertex1, &vertex1, et3); ASSERT_TRUE(res3.HasValue()); auto edge3 = res3.GetValue(); ASSERT_EQ(edge3.EdgeType(), et3); ASSERT_EQ(edge3.FromVertex(), vertex1); ASSERT_EQ(edge3.ToVertex(), vertex1); - auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4); + auto res4 = acc->CreateEdge(&vertex2, &vertex2, et4); ASSERT_TRUE(res4.HasValue()); auto edge4 = res4.GetValue(); ASSERT_EQ(edge4.EdgeType(), et4); @@ -4101,32 +4118,32 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex, but abort the transaction { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); ASSERT_TRUE(vertex2); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex1); + auto ret = acc->DeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex1); + auto ret = acc->DetachDeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -4239,21 +4256,21 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { ASSERT_EQ(e.ToVertex(), *vertex2); } - acc.Abort(); + acc->Abort(); } // Check dataset { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); ASSERT_TRUE(vertex2); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); // Check edges { @@ -4417,32 +4434,32 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Detach delete vertex { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); ASSERT_TRUE(vertex2); - auto et1 = acc.NameToEdgeType("et1"); - auto et2 = acc.NameToEdgeType("et2"); - auto et3 = acc.NameToEdgeType("et3"); - auto et4 = acc.NameToEdgeType("et4"); + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); // Delete must fail { - auto ret = acc.DeleteVertex(&*vertex1); + auto ret = acc->DeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasError()); ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); } // Detach delete vertex { - auto ret = acc.DetachDeleteVertex(&*vertex1); + auto ret = acc->DetachDeleteVertex(&*vertex1); ASSERT_TRUE(ret.HasValue()); ASSERT_TRUE(*ret); } @@ -4555,18 +4572,18 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { ASSERT_EQ(e.ToVertex(), *vertex2); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check dataset { - auto acc = store.Access(); - auto vertex1 = acc.FindVertex(gid_vertex1, memgraph::storage::View::NEW); - auto vertex2 = acc.FindVertex(gid_vertex2, memgraph::storage::View::NEW); + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); ASSERT_TRUE(vertex2); - auto et4 = acc.NameToEdgeType("et4"); + auto et4 = acc->NameToEdgeType("et4"); // Check edges { @@ -4618,19 +4635,20 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyCommit) { - memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); @@ -4661,15 +4679,15 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -4685,20 +4703,20 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); { auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); @@ -4722,56 +4740,57 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_TRUE(old_value->IsNull()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex. { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Set property 5 to "nandare", but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); @@ -4802,39 +4821,39 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - acc.Abort(); + acc->Abort(); } // Check that property 5 is null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); @@ -4865,17 +4884,17 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that property 5 is "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -4891,22 +4910,22 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to null, but abort the transaction. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -4938,17 +4957,17 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - acc.Abort(); + acc->Abort(); } // Check that property 5 is "nandare". { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -4964,22 +4983,22 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } // Set property 5 to null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); { @@ -5011,59 +5030,60 @@ TEST(StorageWithProperties, EdgePropertyAbort) { ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Check that property 5 is null. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertySerializationError) { - memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store.Access(); - auto acc2 = store.Access(); + auto acc1 = store->Access(); + auto acc2 = store->Access(); // Set property 1 to 123 in accessor 1. { - auto vertex = acc1.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property1 = acc1.NameToProperty("property1"); - auto property2 = acc1.NameToProperty("property2"); + auto property1 = acc1->NameToProperty("property1"); + auto property2 = acc1->NameToProperty("property2"); ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); @@ -5092,12 +5112,12 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { // Set property 2 to "nandare" in accessor 2. { - auto vertex = acc2.FindVertex(gid, memgraph::storage::View::OLD); + auto vertex = acc2->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property1 = acc2.NameToProperty("property1"); - auto property2 = acc2.NameToProperty("property2"); + auto property1 = acc2->NameToProperty("property1"); + auto property2 = acc2->NameToProperty("property2"); ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); @@ -5114,18 +5134,18 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { } // Finalize both accessors. - ASSERT_FALSE(acc1.Commit().HasError()); - acc2.Abort(); + ASSERT_FALSE(acc1->Commit().HasError()); + acc2->Abort(); // Check which properties exist. { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property1 = acc.NameToProperty("property1"); - auto property2 = acc.NameToProperty("property2"); + auto property1 = acc->NameToProperty("property1"); + auto property2 = acc->NameToProperty("property2"); ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::OLD)->ValueInt(), 123); ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); @@ -5143,21 +5163,22 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { ASSERT_EQ(properties[property1].ValueInt(), 123); } - acc.Abort(); + acc->Abort(); } } TEST(StorageWithProperties, EdgePropertyClear) { - memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); memgraph::storage::Gid gid; - auto property1 = store.NameToProperty("property1"); - auto property2 = store.NameToProperty("property2"); + auto property1 = store->NameToProperty("property1"); + auto property2 = store->NameToProperty("property2"); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); @@ -5166,11 +5187,11 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; @@ -5199,11 +5220,11 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; @@ -5211,11 +5232,11 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; @@ -5245,11 +5266,11 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; @@ -5257,32 +5278,33 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); - acc.Abort(); + acc->Abort(); } } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithoutProperties, EdgePropertyAbort) { - memgraph::storage::Storage store({.items = {.properties_on_edges = false}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); @@ -5305,15 +5327,15 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - acc.Abort(); + acc->Abort(); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; - auto property = acc.NameToProperty("property5"); + auto property = acc->NameToProperty("property5"); ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); @@ -5321,49 +5343,51 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = acc->NameToProperty("other"); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); - acc.Abort(); + acc->Abort(); } } TEST(StorageWithoutProperties, EdgePropertyClear) { - memgraph::storage::Storage store({.items = {.properties_on_edges = false}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}})); memgraph::storage::Gid gid; { - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); gid = vertex.Gid(); - auto et = acc.NameToEdgeType("et5"); - auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); ASSERT_EQ(edge.EdgeType(), et); ASSERT_EQ(edge.FromVertex(), vertex); ASSERT_EQ(edge.ToVertex(), vertex); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, memgraph::storage::View::OLD); + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; ASSERT_EQ(edge.ClearProperties().GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); - acc.Abort(); + acc->Abort(); } } TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { - memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + std::unique_ptr<memgraph::storage::Storage> store( + new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); - auto property = store.NameToProperty("property"); + auto property = store->NameToProperty("property"); - auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - auto edge = acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")); + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge")); ASSERT_TRUE(edge.HasValue()); // Check state before (OLD view). @@ -5387,5 +5411,5 @@ TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { ASSERT_EQ(edge->Properties(memgraph::storage::View::NEW)->size(), 1); ASSERT_EQ(*edge->GetProperty(property, memgraph::storage::View::NEW), memgraph::storage::PropertyValue("value")); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } diff --git a/tests/unit/storage_v2_edge_ondisk.cpp b/tests/unit/storage_v2_edge_ondisk.cpp new file mode 100644 index 000000000..4edc099d1 --- /dev/null +++ b/tests/unit/storage_v2_edge_ondisk.cpp @@ -0,0 +1,5815 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <limits> + +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/storage.hpp" + +using testing::UnorderedElementsAre; + +class StorageEdgeTest : public ::testing::TestWithParam<bool> {}; + +INSTANTIATE_TEST_CASE_P(EdgesWithProperties, StorageEdgeTest, ::testing::Values(true)); +INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Values(false)); + +const std::string testSuite = "storage_v2_edge_ondisk"; + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); + gid_to = vertex_to.Gid(); + gid_from = vertex_from.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid_vertex = vertex.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex, &*vertex, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); + gid_to = vertex_to.Gid(); + gid_from = vertex_from.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid_vertex = vertex.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex, &*vertex, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex, &*vertex, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_to = acc->CreateVertex(); + auto vertex_from = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid_vertex = vertex.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex, &*vertex, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex_from, &*vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex_from->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_from)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD, {}, &*vertex_to)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid_vertex = vertex.Gid(); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Create edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&*vertex, &*vertex, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge, but abort the transaction + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + + acc->Abort(); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Delete the edge + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto et = acc->NameToEdgeType("et5"); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto res = acc->DeleteEdge(&edge); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + { + auto ret = vertex->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + auto other_et = acc->NameToEdgeType("other"); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {}, &*vertex)->size(), 1); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD, {other_et}, &*vertex)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&vertex_from, &vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex_from); + ASSERT_EQ(edge.ToVertex(), vertex_to); + + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + + // Check edges + ASSERT_EQ(vertex_from.InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from.InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from.OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + { + auto ret = vertex_to.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to.InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + ASSERT_EQ(vertex_to.OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to.OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->OutDegree(memgraph::storage::View::NEW).GetError(), + memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_FALSE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); + + gid_vertex1 = vertex1.Gid(); + gid_vertex2 = vertex2.Gid(); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + auto res1 = acc->CreateEdge(&vertex1, &vertex2, et1); + ASSERT_TRUE(res1.HasValue()); + auto edge1 = res1.GetValue(); + ASSERT_EQ(edge1.EdgeType(), et1); + ASSERT_EQ(edge1.FromVertex(), vertex1); + ASSERT_EQ(edge1.ToVertex(), vertex2); + + auto res2 = acc->CreateEdge(&vertex2, &vertex1, et2); + ASSERT_TRUE(res2.HasValue()); + auto edge2 = res2.GetValue(); + ASSERT_EQ(edge2.EdgeType(), et2); + ASSERT_EQ(edge2.FromVertex(), vertex2); + ASSERT_EQ(edge2.ToVertex(), vertex1); + + auto res3 = acc->CreateEdge(&vertex1, &vertex1, et3); + ASSERT_TRUE(res3.HasValue()); + auto edge3 = res3.GetValue(); + ASSERT_EQ(edge3.EdgeType(), et3); + ASSERT_EQ(edge3.FromVertex(), vertex1); + ASSERT_EQ(edge3.ToVertex(), vertex1); + + auto res4 = acc->CreateEdge(&vertex2, &vertex2, et4); + ASSERT_TRUE(res4.HasValue()); + auto edge4 = res4.GetValue(); + ASSERT_EQ(edge4.EdgeType(), et4); + ASSERT_EQ(edge4.FromVertex(), vertex2); + ASSERT_EQ(edge4.ToVertex(), vertex2); + + // Check edges + { + auto ret = vertex1.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1.InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex1.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1.OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex2.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2.InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + { + auto ret = vertex2.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2.OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex1); + acc->PrefetchInEdges(*vertex1); + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + { + auto ret = vertex1->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->OutDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_FALSE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et4 = acc->NameToEdgeType("et4"); + + // Check edges + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store->Access(); + auto vertex_from = acc->CreateVertex(); + auto vertex_to = acc->CreateVertex(); + + auto et = acc->NameToEdgeType("et5"); + + auto res = acc->CreateEdge(&vertex_from, &vertex_to, et); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex_from); + ASSERT_EQ(edge.ToVertex(), vertex_to); + + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + + // Check edges + ASSERT_EQ(vertex_from.InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from.InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from.OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + { + auto ret = vertex_to.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to.InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + ASSERT_EQ(vertex_to.OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to.OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex, but abort the transaction + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->OutDegree(memgraph::storage::View::NEW).GetError(), + memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + acc->Abort(); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Check edges + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::NEW), 0); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + auto et = acc->NameToEdgeType("et5"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->OutDegree(memgraph::storage::View::NEW).GetError(), + memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); + auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); + ASSERT_FALSE(vertex_from); + ASSERT_TRUE(vertex_to); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex_from); + acc->PrefetchInEdges(*vertex_from); + acc->PrefetchOutEdges(*vertex_to); + acc->PrefetchInEdges(*vertex_to); + + // Check edges + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(memgraph::storage::View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(memgraph::storage::View::NEW), 0); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = GetParam(); + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store->Access(); + auto vertex1 = acc->CreateVertex(); + auto vertex2 = acc->CreateVertex(); + + gid_vertex1 = vertex1.Gid(); + gid_vertex2 = vertex2.Gid(); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + auto res1 = acc->CreateEdge(&vertex1, &vertex2, et1); + ASSERT_TRUE(res1.HasValue()); + auto edge1 = res1.GetValue(); + ASSERT_EQ(edge1.EdgeType(), et1); + ASSERT_EQ(edge1.FromVertex(), vertex1); + ASSERT_EQ(edge1.ToVertex(), vertex2); + + auto res2 = acc->CreateEdge(&vertex2, &vertex1, et2); + ASSERT_TRUE(res2.HasValue()); + auto edge2 = res2.GetValue(); + ASSERT_EQ(edge2.EdgeType(), et2); + ASSERT_EQ(edge2.FromVertex(), vertex2); + ASSERT_EQ(edge2.ToVertex(), vertex1); + + auto res3 = acc->CreateEdge(&vertex1, &vertex1, et3); + ASSERT_TRUE(res3.HasValue()); + auto edge3 = res3.GetValue(); + ASSERT_EQ(edge3.EdgeType(), et3); + ASSERT_EQ(edge3.FromVertex(), vertex1); + ASSERT_EQ(edge3.ToVertex(), vertex1); + + auto res4 = acc->CreateEdge(&vertex2, &vertex2, et4); + ASSERT_TRUE(res4.HasValue()); + auto edge4 = res4.GetValue(); + ASSERT_EQ(edge4.EdgeType(), et4); + ASSERT_EQ(edge4.FromVertex(), vertex2); + ASSERT_EQ(edge4.ToVertex(), vertex2); + + // Check edges + { + auto ret = vertex1.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1.InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex1.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1.OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex2.InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2.InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + { + auto ret = vertex2.OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2.OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex, but abort the transaction + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex1); + acc->PrefetchInEdges(*vertex1); + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + { + auto ret = vertex1->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->OutDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + acc->Abort(); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex1); + acc->PrefetchInEdges(*vertex1); + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + // Check edges + { + auto ret = vertex1->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Detach delete vertex + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex1); + acc->PrefetchInEdges(*vertex1); + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et1 = acc->NameToEdgeType("et1"); + auto et2 = acc->NameToEdgeType("et2"); + auto et3 = acc->NameToEdgeType("et3"); + auto et4 = acc->NameToEdgeType("et4"); + + // Delete must fail + { + auto ret = acc->DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc->DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + { + auto ret = vertex1->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->InDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex1->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et3); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + ASSERT_EQ(vertex1->OutDegree(memgraph::storage::View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et1); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); }); + ASSERT_EQ(edges.size(), 2); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 2); + { + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et2); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto e = edges[1]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check dataset + { + auto acc = store->Access(); + auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); + auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); + ASSERT_FALSE(vertex1); + ASSERT_TRUE(vertex2); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex2); + acc->PrefetchInEdges(*vertex2); + + auto et4 = acc->NameToEdgeType("et4"); + + // Check edges + { + auto ret = vertex2->InEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->InEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->InDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges(memgraph::storage::View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex2->OutDegree(memgraph::storage::View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et4); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageWithProperties, EdgePropertyCommit) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = true; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "temporary"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageWithProperties, EdgePropertyAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = true; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create the vertex. + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Set property 5 to "nandare", but abort the transaction. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "temporary"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + acc->Abort(); + } + + // Check that property 5 is null. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + + // Set property 5 to "nandare". + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "temporary"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check that property 5 is "nandare". + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + + // Set property 5 to null, but abort the transaction. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + acc->Abort(); + } + + // Check that property 5 is "nandare". + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + + // Set property 5 to null. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::NEW)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + { + auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(edge.GetProperty(property, memgraph::storage::View::OLD)->ValueString(), "nandare"); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + + // Check that property 5 is null. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageWithProperties, EdgePropertySerializationError) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = true; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + ASSERT_FALSE(acc->Commit().HasError()); + } + + auto acc1 = store->Access(); + auto acc2 = store->Access(); + + // Set property 1 to 123 in accessor 1. + { + auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc1->PrefetchOutEdges(*vertex); + acc1->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property1 = acc1->NameToProperty("property1"); + auto property2 = acc1->NameToProperty("property2"); + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue(123)); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); + ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::NEW)->ValueInt(), 123); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + } + + // Set property 2 to "nandare" in accessor 2. + { + auto vertex = acc2->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc2->PrefetchOutEdges(*vertex); + acc2->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property1 = acc2->NameToProperty("property1"); + auto property2 = acc2->NameToProperty("property2"); + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + // Conflict write. + // DiskStorage has SNAPSHOT isolation level by default, so it will not see this change until commit. + // DiskStorage has optimistic transactions so it will fail on Commit. + auto res = edge.SetProperty(property2, memgraph::storage::PropertyValue("nandare")); + ASSERT_FALSE(res.HasError()); + } + } + + // Finalize both accessors. + ASSERT_FALSE(acc1->Commit().HasError()); + auto res = acc2->Commit(); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(std::get<memgraph::storage::SerializationError>(res.GetError()), memgraph::storage::SerializationError()); + + // Check which properties exist. + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property1 = acc->NameToProperty("property1"); + auto property2 = acc->NameToProperty("property2"); + + ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::OLD)->ValueInt(), 123); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); + { + auto properties = edge.Properties(memgraph::storage::View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + + ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::NEW)->ValueInt(), 123); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + { + auto properties = edge.Properties(memgraph::storage::View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +TEST(StorageWithProperties, EdgePropertyClear) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = true; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid; + auto property1 = store->NameToProperty("property1"); + auto property2 = store->NameToProperty("property2"); + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + + auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue("value")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::OLD)->ValueString(), "value"); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::OLD)->IsNull()); + ASSERT_THAT(edge.Properties(memgraph::storage::View::OLD).GetValue(), + UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value")))); + + { + auto old_values = edge.ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_FALSE(old_values->empty()); + } + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); + + { + auto old_values = edge.ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_TRUE(old_values->empty()); + } + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); + + acc->Abort(); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto old_value = edge.SetProperty(property2, memgraph::storage::PropertyValue(42)); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + ASSERT_EQ(edge.GetProperty(property1, memgraph::storage::View::OLD)->ValueString(), "value"); + ASSERT_EQ(edge.GetProperty(property2, memgraph::storage::View::OLD)->ValueInt(), 42); + ASSERT_THAT(edge.Properties(memgraph::storage::View::OLD).GetValue(), + UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value")), + std::pair(property2, memgraph::storage::PropertyValue(42)))); + + { + auto old_values = edge.ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_FALSE(old_values->empty()); + } + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); + + { + auto old_values = edge.ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_TRUE(old_values->empty()); + } + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); + + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + ASSERT_TRUE(edge.GetProperty(property1, memgraph::storage::View::NEW)->IsNull()); + ASSERT_TRUE(edge.GetProperty(property2, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW).GetValue().size(), 0); + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageWithoutProperties, EdgePropertyAbort) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = false; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); + } + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + { + auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); + } + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + acc->Abort(); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + auto property = acc->NameToProperty("property5"); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::OLD)->size(), 0); + + ASSERT_TRUE(edge.GetProperty(property, memgraph::storage::View::NEW)->IsNull()); + ASSERT_EQ(edge.Properties(memgraph::storage::View::NEW)->size(), 0); + + auto other_property = acc->NameToProperty("other"); + + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::OLD)->IsNull()); + ASSERT_TRUE(edge.GetProperty(other_property, memgraph::storage::View::NEW)->IsNull()); + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +TEST(StorageWithoutProperties, EdgePropertyClear) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = false; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + memgraph::storage::Gid gid; + { + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + gid = vertex.Gid(); + auto et = acc->NameToEdgeType("et5"); + auto edge = acc->CreateEdge(&vertex, &vertex, et).GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.FromVertex(), vertex); + ASSERT_EQ(edge.ToVertex(), vertex); + ASSERT_FALSE(acc->Commit().HasError()); + } + { + auto acc = store->Access(); + auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); + ASSERT_TRUE(vertex); + // We prefetch edges implicitly when go thorough query Accessor + acc->PrefetchOutEdges(*vertex); + acc->PrefetchInEdges(*vertex); + + auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue()[0]; + + ASSERT_EQ(edge.ClearProperties().GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); + + acc->Abort(); + } + disk_test_utils::RemoveRocksDbDirs(testSuite); +} + +TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.items.properties_on_edges = true; + std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); + + auto property = store->NameToProperty("property"); + + auto acc = store->Access(); + auto vertex = acc->CreateVertex(); + auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge")); + ASSERT_TRUE(edge.HasValue()); + + // Check state before (OLD view). + ASSERT_EQ(edge->Properties(memgraph::storage::View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); + ASSERT_EQ(edge->GetProperty(property, memgraph::storage::View::OLD).GetError(), + memgraph::storage::Error::NONEXISTENT_OBJECT); + + // Check state before (NEW view). + ASSERT_EQ(edge->Properties(memgraph::storage::View::NEW)->size(), 0); + ASSERT_EQ(*edge->GetProperty(property, memgraph::storage::View::NEW), memgraph::storage::PropertyValue()); + + // Modify edge. + ASSERT_TRUE(edge->SetProperty(property, memgraph::storage::PropertyValue("value"))->IsNull()); + + // Check state after (OLD view). + ASSERT_EQ(edge->Properties(memgraph::storage::View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); + ASSERT_EQ(edge->GetProperty(property, memgraph::storage::View::OLD).GetError(), + memgraph::storage::Error::NONEXISTENT_OBJECT); + + // Check state after (NEW view). + ASSERT_EQ(edge->Properties(memgraph::storage::View::NEW)->size(), 1); + ASSERT_EQ(*edge->GetProperty(property, memgraph::storage::View::NEW), memgraph::storage::PropertyValue("value")); + + ASSERT_FALSE(acc->Commit().HasError()); + disk_test_utils::RemoveRocksDbDirs(testSuite); +} diff --git a/tests/unit/storage_v2_gc.cpp b/tests/unit/storage_v2_gc.cpp index 401e1a155..8074e833c 100644 --- a/tests/unit/storage_v2_gc.cpp +++ b/tests/unit/storage_v2_gc.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,7 +12,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> -#include "storage/v2/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" using testing::UnorderedElementsAre; @@ -24,26 +24,27 @@ using testing::UnorderedElementsAre; // then verify that GC didn't delete anything it shouldn't have. // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageV2Gc, Sanity) { - memgraph::storage::Storage storage(memgraph::storage::Config{ - .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}}); + std::unique_ptr<memgraph::storage::Storage> storage( + std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{ + .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}})); std::vector<memgraph::storage::Gid> vertices; { - auto acc = storage.Access(); + auto acc = storage->Access(); // Create some vertices, but delete some of them immediately. for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.CreateVertex(); + auto vertex = acc->CreateVertex(); vertices.push_back(vertex.Gid()); } - acc.AdvanceCommand(); + acc->AdvanceCommand(); for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.FindVertex(vertices[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex.has_value()); if (i % 5 == 0) { - EXPECT_FALSE(acc.DeleteVertex(&vertex.value()).HasError()); + EXPECT_FALSE(acc->DeleteVertex(&vertex.value()).HasError()); } } @@ -51,20 +52,20 @@ TEST(StorageV2Gc, Sanity) { std::this_thread::sleep_for(std::chrono::milliseconds(300)); for (uint64_t i = 0; i < 1000; ++i) { - auto vertex_old = acc.FindVertex(vertices[i], memgraph::storage::View::OLD); - auto vertex_new = acc.FindVertex(vertices[i], memgraph::storage::View::NEW); + auto vertex_old = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); + auto vertex_new = acc->FindVertex(vertices[i], memgraph::storage::View::NEW); EXPECT_TRUE(vertex_old.has_value()); EXPECT_EQ(vertex_new.has_value(), i % 5 != 0); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Verify existing vertices and add labels to some of them. { - auto acc = storage.Access(); + auto acc = storage->Access(); for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.FindVertex(vertices[i], memgraph::storage::View::OLD); + auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); EXPECT_EQ(vertex.has_value(), i % 5 != 0); if (vertex.has_value()) { @@ -79,7 +80,7 @@ TEST(StorageV2Gc, Sanity) { // Verify labels. for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.FindVertex(vertices[i], memgraph::storage::View::NEW); + auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::NEW); EXPECT_EQ(vertex.has_value(), i % 5 != 0); if (vertex.has_value()) { @@ -95,32 +96,32 @@ TEST(StorageV2Gc, Sanity) { } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Add and remove some edges. { - auto acc = storage.Access(); + auto acc = storage->Access(); for (uint64_t i = 0; i < 1000; ++i) { - auto from_vertex = acc.FindVertex(vertices[i], memgraph::storage::View::OLD); - auto to_vertex = acc.FindVertex(vertices[(i + 1) % 1000], memgraph::storage::View::OLD); + auto from_vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); + auto to_vertex = acc->FindVertex(vertices[(i + 1) % 1000], memgraph::storage::View::OLD); EXPECT_EQ(from_vertex.has_value(), i % 5 != 0); EXPECT_EQ(to_vertex.has_value(), (i + 1) % 5 != 0); if (from_vertex.has_value() && to_vertex.has_value()) { EXPECT_FALSE( - acc.CreateEdge(&from_vertex.value(), &to_vertex.value(), memgraph::storage::EdgeTypeId::FromUint(i)) + acc->CreateEdge(&from_vertex.value(), &to_vertex.value(), memgraph::storage::EdgeTypeId::FromUint(i)) .HasError()); } } // Detach delete some vertices. for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.FindVertex(vertices[i], memgraph::storage::View::NEW); + auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::NEW); EXPECT_EQ(vertex.has_value(), i % 5 != 0); if (vertex.has_value()) { if (i % 3 == 0) { - EXPECT_FALSE(acc.DetachDeleteVertex(&vertex.value()).HasError()); + EXPECT_FALSE(acc->DetachDeleteVertex(&vertex.value()).HasError()); } } } @@ -130,7 +131,7 @@ TEST(StorageV2Gc, Sanity) { // Vertify edges. for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc.FindVertex(vertices[i], memgraph::storage::View::NEW); + auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::NEW); EXPECT_EQ(vertex.has_value(), i % 5 != 0 && i % 3 != 0); if (vertex.has_value()) { auto out_edges = vertex->OutEdges(memgraph::storage::View::NEW); @@ -153,7 +154,7 @@ TEST(StorageV2Gc, Sanity) { } } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -166,33 +167,34 @@ TEST(StorageV2Gc, Sanity) { // transaction 1 can still see them with that label. // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageV2Gc, Indices) { - memgraph::storage::Storage storage(memgraph::storage::Config{ - .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}}); + std::unique_ptr<memgraph::storage::Storage> storage( + std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{ + .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}})); - ASSERT_FALSE(storage.CreateIndex(storage.NameToLabel("label")).HasError()); + ASSERT_FALSE(storage->CreateIndex(storage->NameToLabel("label")).HasError()); { - auto acc0 = storage.Access(); + auto acc0 = storage->Access(); for (uint64_t i = 0; i < 1000; ++i) { - auto vertex = acc0.CreateVertex(); - ASSERT_TRUE(*vertex.AddLabel(acc0.NameToLabel("label"))); + auto vertex = acc0->CreateVertex(); + ASSERT_TRUE(*vertex.AddLabel(acc0->NameToLabel("label"))); } - ASSERT_FALSE(acc0.Commit().HasError()); + ASSERT_FALSE(acc0->Commit().HasError()); } { - auto acc1 = storage.Access(); + auto acc1 = storage->Access(); - auto acc2 = storage.Access(); - for (auto vertex : acc2.Vertices(memgraph::storage::View::OLD)) { - ASSERT_TRUE(*vertex.RemoveLabel(acc2.NameToLabel("label"))); + auto acc2 = storage->Access(); + for (auto vertex : acc2->Vertices(memgraph::storage::View::OLD)) { + ASSERT_TRUE(*vertex.RemoveLabel(acc2->NameToLabel("label"))); } - ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc2->Commit().HasError()); // Wait for GC. std::this_thread::sleep_for(std::chrono::milliseconds(300)); std::set<memgraph::storage::Gid> gids; - for (auto vertex : acc1.Vertices(acc1.NameToLabel("label"), memgraph::storage::View::OLD)) { + for (auto vertex : acc1->Vertices(acc1->NameToLabel("label"), memgraph::storage::View::OLD)) { gids.insert(vertex.Gid()); } EXPECT_EQ(gids.size(), 1000); diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp index eb05277f7..9805e970f 100644 --- a/tests/unit/storage_v2_indices.cpp +++ b/tests/unit/storage_v2_indices.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,33 +10,51 @@ // licenses/APL.txt. #include <gmock/gmock.h> +#include <gtest/gtest-typed-test.h> #include <gtest/gtest.h> +#include <gtest/internal/gtest-type-util.h> +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/property_value.hpp" -#include "storage/v2/storage.hpp" #include "storage/v2/temporal.hpp" +#include "utils/rocksdb_serialization.hpp" // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; using testing::IsEmpty; +using testing::Types; using testing::UnorderedElementsAre; // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define ASSERT_NO_ERROR(result) ASSERT_FALSE((result).HasError()) +template <typename StorageType> class IndexTest : public testing::Test { protected: void SetUp() override { - auto acc = storage.Access(); - prop_id = acc.NameToProperty("id"); - prop_val = acc.NameToProperty("val"); - label1 = acc.NameToLabel("label1"); - label2 = acc.NameToLabel("label2"); + config_ = disk_test_utils::GenerateOnDiskConfig(testSuite); + this->storage = std::make_unique<StorageType>(config_); + auto acc = this->storage->Access(); + this->prop_id = acc->NameToProperty("id"); + this->prop_val = acc->NameToProperty("val"); + this->label1 = acc->NameToLabel("label1"); + this->label2 = acc->NameToLabel("label2"); vertex_id = 0; } - Storage storage; + void TearDown() override { + if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) { + disk_test_utils::RemoveRocksDbDirs(testSuite); + } + this->storage.reset(nullptr); + } + + const std::string testSuite = "storage_v2_indices"; + memgraph::storage::Config config_; + std::unique_ptr<memgraph::storage::Storage> storage; PropertyId prop_id; PropertyId prop_val; LabelId label1; @@ -44,7 +62,7 @@ class IndexTest : public testing::Test { VertexAccessor CreateVertex(Storage::Accessor *accessor) { VertexAccessor vertex = accessor->CreateVertex(); - MG_ASSERT(!vertex.SetProperty(prop_id, PropertyValue(vertex_id++)).HasError()); + MG_ASSERT(!vertex.SetProperty(this->prop_id, PropertyValue(vertex_id++)).HasError()); return vertex; } @@ -52,7 +70,7 @@ class IndexTest : public testing::Test { std::vector<int64_t> GetIds(TIterable iterable, View view = View::OLD) { std::vector<int64_t> ret; for (auto vertex : iterable) { - ret.push_back(vertex.GetProperty(prop_id, view)->ValueInt()); + ret.push_back(vertex.GetProperty(this->prop_id, view)->ValueInt()); } return ret; } @@ -61,165 +79,171 @@ class IndexTest : public testing::Test { int vertex_id; }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; + +TYPED_TEST_CASE(IndexTest, StorageTypes); +// TYPED_TEST_CASE(IndexTest, InMemoryStorageType); + // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexCreate) { +TYPED_TEST(IndexTest, LabelIndexCreate) { { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelIndexExists(label1)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelIndexExists(this->label1)); } - EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(this->storage->ListAllIndices().label.size(), 0); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 10; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); { - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); } { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 10; i < 20; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + /// Vertices needs to go to label index rocksdb + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - acc.Abort(); + acc->Abort(); } { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 10; i < 20; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexDrop) { +TYPED_TEST(IndexTest, LabelIndexDrop) { { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelIndexExists(label1)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelIndexExists(this->label1)); } - EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(this->storage->ListAllIndices().label.size(), 0); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 10; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); { - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); } - EXPECT_FALSE(storage.DropIndex(label1).HasError()); + EXPECT_FALSE(this->storage->DropIndex(this->label1).HasError()); { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelIndexExists(label1)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelIndexExists(this->label1)); } - EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(this->storage->ListAllIndices().label.size(), 0); - EXPECT_TRUE(storage.DropIndex(label1).HasError()); + EXPECT_TRUE(this->storage->DropIndex(this->label1).HasError()); { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelIndexExists(label1)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelIndexExists(this->label1)); } - EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(this->storage->ListAllIndices().label.size(), 0); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 10; i < 20; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); { - auto acc = storage.Access(); - EXPECT_TRUE(acc.LabelIndexExists(label1)); + auto acc = this->storage->Access(); + EXPECT_TRUE(acc->LabelIndexExists(this->label1)); } - EXPECT_THAT(storage.ListAllIndices().label, UnorderedElementsAre(label1)); + EXPECT_THAT(this->storage->ListAllIndices().label, UnorderedElementsAre(this->label1)); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexBasic) { +TYPED_TEST(IndexTest, LabelIndexBasic) { // The following steps are performed and index correctness is validated after // each step: // 1. Create 10 vertices numbered from 0 to 9. @@ -227,186 +251,294 @@ TEST_F(IndexTest, LabelIndexBasic) { // 3. Remove Label1 from odd numbered vertices, and add it to even numbered // vertices. // 4. Delete even numbered vertices. - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); - EXPECT_FALSE(storage.CreateIndex(label2).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2).HasError()); - auto acc = storage.Access(); - EXPECT_THAT(storage.ListAllIndices().label, UnorderedElementsAre(label1, label2)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); + auto acc = this->storage->Access(); + EXPECT_THAT(this->storage->ListAllIndices().label, UnorderedElementsAre(this->label1, this->label2)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), IsEmpty()); for (int i = 0; i < 10; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); + auto vertex = this->CreateVertex(acc.get()); + spdlog::debug("Created vertex with gid: {}", memgraph::utils::SerializeIdType(vertex.Gid())); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); - acc.AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + acc->AdvanceCommand(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); - for (auto vertex : acc.Vertices(View::OLD)) { - int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + for (auto vertex : acc->Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(this->prop_id, View::OLD)->ValueInt(); if (id % 2) { - ASSERT_NO_ERROR(vertex.RemoveLabel(label1)); + ASSERT_NO_ERROR(vertex.RemoveLabel(this->label1)); } else { - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); } } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); - for (auto vertex : acc.Vertices(View::OLD)) { - int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + for (auto vertex : acc->Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(this->prop_id, View::OLD)->ValueInt(); if (id % 2 == 0) { - ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), IsEmpty()); - acc.AdvanceCommand(); + acc->AdvanceCommand(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::NEW), View::NEW), IsEmpty()); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexDuplicateVersions) { +TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) { // By removing labels and adding them again we create duplicate entries for // the same vertex in the index (they only differ by the timestamp). This test // checks that duplicates are properly filtered out. - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); - EXPECT_FALSE(storage.CreateIndex(label2).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2).HasError()); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 5; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.RemoveLabel(label1)); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.RemoveLabel(this->label1)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexTransactionalIsolation) { +// passes +TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) { // Check that transactions only see entries they are supposed to see. - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); - EXPECT_FALSE(storage.CreateIndex(label2).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2).HasError()); - auto acc_before = storage.Access(); - auto acc = storage.Access(); - auto acc_after = storage.Access(); + auto acc_before = this->storage->Access(); + auto acc = this->storage->Access(); + auto acc_after = this->storage->Access(); for (int i = 0; i < 5; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); } - EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); - ASSERT_NO_ERROR(acc.Commit()); + EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); - auto acc_after_commit = storage.Access(); + EXPECT_THAT(this->GetIds(acc_after->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after_commit.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + ASSERT_NO_ERROR(acc->Commit()); + + auto acc_after_commit = this->storage->Access(); + + EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after_commit->Vertices(this->label1, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelIndexCountEstimate) { - EXPECT_FALSE(storage.CreateIndex(label1).HasError()); - EXPECT_FALSE(storage.CreateIndex(label2).HasError()); +TYPED_TEST(IndexTest, LabelIndexCountEstimate) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2).HasError()); - auto acc = storage.Access(); - for (int i = 0; i < 20; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 3 ? label1 : label2)); + auto acc = this->storage->Access(); + for (int i = 0; i < 20; ++i) { + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 3 ? this->label1 : this->label2)); + } + + EXPECT_EQ(acc->ApproximateVertexCount(this->label1), 13); + EXPECT_EQ(acc->ApproximateVertexCount(this->label2), 7); } +} - EXPECT_EQ(acc.ApproximateVertexCount(label1), 13); - EXPECT_EQ(acc.ApproximateVertexCount(label2), 7); +TYPED_TEST(IndexTest, LabelIndexDeletedVertex) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + auto acc1 = this->storage->Access(); + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + ASSERT_NO_ERROR(acc1->Commit()); + auto acc2 = this->storage->Access(); + auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto res = acc2->DeleteVertex(&*vertex_to_delete); + ASSERT_FALSE(res.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + auto acc3 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1)); + } +} + +TYPED_TEST(IndexTest, LabelIndexRemoveIndexedLabel) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + auto acc1 = this->storage->Access(); + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(acc1->Commit()); + auto acc2 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto res = vertex_to_delete->RemoveLabel(this->label1); + ASSERT_FALSE(res.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + auto acc3 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1)); + } +} + +TYPED_TEST(IndexTest, LabelIndexRemoveAndAddIndexedLabel) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + auto acc1 = this->storage->Access(); + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(acc1->Commit()); + auto acc2 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto res_remove = vertex_to_delete->RemoveLabel(this->label1); + ASSERT_FALSE(res_remove.HasError()); + auto res_add = vertex_to_delete->AddLabel(this->label1); + ASSERT_FALSE(res_add.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + auto acc3 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + } +} + +TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + auto *disk_label_index = + static_cast<memgraph::storage::DiskLabelIndex *>(this->storage->indices_.label_index_.get()); + + EXPECT_FALSE(this->storage->CreateIndex(this->label1).HasError()); + auto acc1 = this->storage->Access(); + auto vertex = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10))); + ASSERT_NO_ERROR(acc1->Commit()); + + auto *tx_db = disk_label_index->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = this->storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc3 = this->storage->Access(std::nullopt); + auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue()); + ASSERT_FALSE(acc3->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexCreateAndDrop) { - EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); - EXPECT_FALSE(storage.CreateIndex(label1, prop_id).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexCreateAndDrop) { + EXPECT_EQ(this->storage->ListAllIndices().label_property.size(), 0); + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_id).HasError()); { - auto acc = storage.Access(); - EXPECT_TRUE(acc.LabelPropertyIndexExists(label1, prop_id)); + auto acc = this->storage->Access(); + EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label1, this->prop_id)); } - EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label1, prop_id))); + EXPECT_THAT(this->storage->ListAllIndices().label_property, + UnorderedElementsAre(std::make_pair(this->label1, this->prop_id))); { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelPropertyIndexExists(label2, prop_id)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } - EXPECT_TRUE(storage.CreateIndex(label1, prop_id).HasError()); - EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label1, prop_id))); + EXPECT_TRUE(this->storage->CreateIndex(this->label1, this->prop_id).HasError()); + EXPECT_THAT(this->storage->ListAllIndices().label_property, + UnorderedElementsAre(std::make_pair(this->label1, this->prop_id))); - EXPECT_FALSE(storage.CreateIndex(label2, prop_id).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2, this->prop_id).HasError()); { - auto acc = storage.Access(); - EXPECT_TRUE(acc.LabelPropertyIndexExists(label2, prop_id)); + auto acc = this->storage->Access(); + EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } - EXPECT_THAT(storage.ListAllIndices().label_property, - UnorderedElementsAre(std::make_pair(label1, prop_id), std::make_pair(label2, prop_id))); + EXPECT_THAT( + this->storage->ListAllIndices().label_property, + UnorderedElementsAre(std::make_pair(this->label1, this->prop_id), std::make_pair(this->label2, this->prop_id))); - EXPECT_FALSE(storage.DropIndex(label1, prop_id).HasError()); + EXPECT_FALSE(this->storage->DropIndex(this->label1, this->prop_id).HasError()); { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelPropertyIndexExists(label1, prop_id)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label1, this->prop_id)); } - EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label2, prop_id))); - EXPECT_TRUE(storage.DropIndex(label1, prop_id).HasError()); + EXPECT_THAT(this->storage->ListAllIndices().label_property, + UnorderedElementsAre(std::make_pair(this->label2, this->prop_id))); + EXPECT_TRUE(this->storage->DropIndex(this->label1, this->prop_id).HasError()); - EXPECT_FALSE(storage.DropIndex(label2, prop_id).HasError()); + EXPECT_FALSE(this->storage->DropIndex(this->label2, this->prop_id).HasError()); { - auto acc = storage.Access(); - EXPECT_FALSE(acc.LabelPropertyIndexExists(label2, prop_id)); + auto acc = this->storage->Access(); + EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } - EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + EXPECT_EQ(this->storage->ListAllIndices().label_property.size(), 0); } // The following three tests are almost an exact copy-paste of the corresponding @@ -415,217 +547,270 @@ TEST_F(IndexTest, LabelPropertyIndexCreateAndDrop) { // test. // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexBasic) { - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); - EXPECT_FALSE(storage.CreateIndex(label2, prop_val).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexBasic) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label2, this->prop_val).HasError()); - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), IsEmpty()); for (int i = 0; i < 10; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2)); - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i))); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(i))); } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), IsEmpty()); - acc.AdvanceCommand(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); - for (auto vertex : acc.Vertices(View::OLD)) { - int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc->Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(this->prop_id, View::OLD)->ValueInt(); if (id % 2) { - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue())); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue())); } else { - ASSERT_NO_ERROR(vertex.AddLabel(label1)); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); } } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); - for (auto vertex : acc.Vertices(View::OLD)) { - int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc->Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(this->prop_id, View::OLD)->ValueInt(); if (id % 2 == 0) { - ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9)); - acc.AdvanceCommand(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 2, 4, 6, 8)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + acc->AdvanceCommand(); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::OLD), View::OLD), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, this->prop_val, View::NEW), View::NEW), IsEmpty()); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexDuplicateVersions) { - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 5; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i))); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(i))); } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + auto acc = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue())); + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue())); } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); - for (auto vertex : acc.Vertices(View::OLD)) { - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(42))); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + for (auto vertex : acc->Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(42))); } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); + + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexTransactionalIsolation) { - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexTransactionalIsolation) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); - auto acc_before = storage.Access(); - auto acc = storage.Access(); - auto acc_after = storage.Access(); + auto acc_before = this->storage->Access(); + auto acc = this->storage->Access(); + auto acc_after = this->storage->Access(); for (int i = 0; i < 5; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i))); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(i))); } - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); - EXPECT_THAT(GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); - ASSERT_NO_ERROR(acc.Commit()); + EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); - auto acc_after_commit = storage.Access(); + EXPECT_THAT(this->GetIds(acc_after->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); - EXPECT_THAT(GetIds(acc_after_commit.Vertices(label1, prop_val, View::NEW), View::NEW), + ASSERT_NO_ERROR(acc->Commit()); + + auto acc_after_commit = this->storage->Access(); + + EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); + + EXPECT_THAT(this->GetIds(acc_after_commit->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexFiltering) { +TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) { // We insert vertices with values: // 0 0.0 1 1.0 2 2.0 3 3.0 4 4.0 // Then we check all combinations of inclusive and exclusive bounds. // We also have a mix of doubles and integers to verify that they are sorted // properly. - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 10; ++i) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, i % 2 ? PropertyValue(i / 2) : PropertyValue(i / 2.0))); + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, i % 2 ? PropertyValue(i / 2) : PropertyValue(i / 2.0))); } - ASSERT_NO_ERROR(acc.Commit()); + ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (int i = 0; i < 5; ++i) { - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, PropertyValue(i), View::OLD)), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, PropertyValue(i), View::OLD)), UnorderedElementsAre(2 * i, 2 * i + 1)); } // [1, +inf> - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), - std::nullopt, View::OLD)), - UnorderedElementsAre(2, 3, 4, 5, 6, 7, 8, 9)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + std::nullopt, View::OLD)), + UnorderedElementsAre(2, 3, 4, 5, 6, 7, 8, 9)); + // <1, +inf> - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), - std::nullopt, View::OLD)), - UnorderedElementsAre(4, 5, 6, 7, 8, 9)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + std::nullopt, View::OLD)), + UnorderedElementsAre(4, 5, 6, 7, 8, 9)); // <-inf, 3] - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt, - memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, std::nullopt, + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), UnorderedElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); + // <-inf, 3> - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt, - memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, std::nullopt, + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), UnorderedElementsAre(0, 1, 2, 3, 4, 5)); // [1, 3] - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), - memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), - UnorderedElementsAre(2, 3, 4, 5, 6, 7)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(2, 3, 4, 5, 6, 7)); + // <1, 3] - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), - memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), - UnorderedElementsAre(4, 5, 6, 7)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(4, 5, 6, 7)); + // [1, 3> - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), - memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), - UnorderedElementsAre(2, 3, 4, 5)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(2, 3, 4, 5)); + // <1, 3> - EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), - memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), - UnorderedElementsAre(4, 5)); + EXPECT_THAT( + this->GetIds(acc->Vertices(this->label1, this->prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(4, 5)); } } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(IndexTest, LabelPropertyIndexCountEstimate) { - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexCountEstimate) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); - auto acc = storage.Access(); - for (int i = 1; i <= 10; ++i) { - for (int j = 0; j < i; ++j) { - auto vertex = CreateVertex(&acc); - ASSERT_NO_ERROR(vertex.AddLabel(label1)); - ASSERT_NO_ERROR(vertex.SetProperty(prop_val, PropertyValue(i))); + auto acc = this->storage->Access(); + for (int i = 1; i <= 10; ++i) { + for (int j = 0; j < i; ++j) { + auto vertex = this->CreateVertex(acc.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(i))); + } } - } - EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val), 55); - for (int i = 1; i <= 10; ++i) { - EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val, PropertyValue(i)), i); - } + EXPECT_EQ(acc->ApproximateVertexCount(this->label1, this->prop_val), 55); + for (int i = 1; i <= 10; ++i) { + EXPECT_EQ(acc->ApproximateVertexCount(this->label1, this->prop_val, PropertyValue(i)), i); + } - EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(2)), - memgraph::utils::MakeBoundInclusive(PropertyValue(6))), - 2 + 3 + 4 + 5 + 6); + EXPECT_EQ( + acc->ApproximateVertexCount(this->label1, this->prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(2)), + memgraph::utils::MakeBoundInclusive(PropertyValue(6))), + 2 + 3 + 4 + 5 + 6); + } } -TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { - EXPECT_FALSE(storage.CreateIndex(label1, prop_val).HasError()); +TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); const std::array temporals{TemporalData{TemporalType::Date, 23}, TemporalData{TemporalType::Date, 28}, TemporalData{TemporalType::LocalDateTime, 20}}; @@ -661,24 +846,24 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { // Create vertices, each with one of the values above. { - auto acc = storage.Access(); + auto acc = this->storage->Access(); for (const auto &value : values) { - auto v = acc.CreateVertex(); - ASSERT_TRUE(v.AddLabel(label1).HasValue()); - ASSERT_TRUE(v.SetProperty(prop_val, value).HasValue()); + auto v = acc->CreateVertex(); + ASSERT_TRUE(v.AddLabel(this->label1).HasValue()); + ASSERT_TRUE(v.SetProperty(this->prop_val, value).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // Verify that all nodes are in the index. { - auto acc = storage.Access(); - auto iterable = acc.Vertices(label1, prop_val, View::OLD); + auto acc = this->storage->Access(); + auto iterable = acc->Vertices(this->label1, this->prop_val, View::OLD); auto it = iterable.begin(); for (const auto &value : values) { ASSERT_NE(it, iterable.end()); auto vertex = *it; - auto maybe_value = vertex.GetProperty(prop_val, View::OLD); + auto maybe_value = vertex.GetProperty(this->prop_val, View::OLD); ASSERT_TRUE(maybe_value.HasValue()); ASSERT_EQ(value, *maybe_value); ++it; @@ -689,12 +874,12 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { auto verify = [&](const std::optional<memgraph::utils::Bound<PropertyValue>> &from, const std::optional<memgraph::utils::Bound<PropertyValue>> &to, const std::vector<PropertyValue> &expected) { - auto acc = storage.Access(); - auto iterable = acc.Vertices(label1, prop_val, from, to, View::OLD); + auto acc = this->storage->Access(); + auto iterable = acc->Vertices(this->label1, this->prop_val, from, to, View::OLD); size_t i = 0; for (auto it = iterable.begin(); it != iterable.end(); ++it, ++i) { auto vertex = *it; - auto maybe_value = vertex.GetProperty(prop_val, View::OLD); + auto maybe_value = vertex.GetProperty(this->prop_val, View::OLD); ASSERT_TRUE(maybe_value.HasValue()); ASSERT_EQ(*maybe_value, expected[i]); } @@ -830,3 +1015,117 @@ TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { // Iteration without any bounds should return all items of the index. verify(std::nullopt, std::nullopt, values); } + +TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); + auto acc1 = this->storage->Access(); + + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop_val, PropertyValue(0))); + + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop_val, PropertyValue(1))); + + EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + ASSERT_NO_ERROR(acc1->Commit()); + + auto acc2 = this->storage->Access(); + auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto res = acc2->DeleteVertex(&*vertex_to_delete); + ASSERT_FALSE(res.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + + auto acc3 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(1)); + } +} + +/// TODO: empty lines, so it is easier to read what is actually going on here +TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); + auto acc1 = this->storage->Access(); + + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop_val, PropertyValue(0))); + + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop_val, PropertyValue(1))); + + EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + ASSERT_NO_ERROR(acc1->Commit()); + + auto acc2 = this->storage->Access(); + auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto res = vertex_to_delete->RemoveLabel(this->label1); + ASSERT_FALSE(res.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + + auto acc3 = this->storage->Access(); + EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), + UnorderedElementsAre(1)); + } +} + +TYPED_TEST(IndexTest, LabelPropertyIndexRemoveAndAddIndexedLabel) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); + auto acc1 = this->storage->Access(); + + auto vertex1 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex1.SetProperty(this->prop_val, PropertyValue(0))); + + auto vertex2 = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex2.SetProperty(this->prop_val, PropertyValue(1))); + + EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); + ASSERT_NO_ERROR(acc1->Commit()); + + auto acc2 = this->storage->Access(); + auto target_vertex = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); + auto remove_res = target_vertex->RemoveLabel(this->label1); + ASSERT_FALSE(remove_res.HasError()); + auto add_res = target_vertex->AddLabel(this->label1); + ASSERT_FALSE(add_res.HasError()); + ASSERT_NO_ERROR(acc2->Commit()); + } +} + +TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) { + if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { + auto *disk_label_property_index = + static_cast<memgraph::storage::DiskLabelPropertyIndex *>(this->storage->indices_.label_property_index_.get()); + + EXPECT_FALSE(this->storage->CreateIndex(this->label1, this->prop_val).HasError()); + auto acc1 = this->storage->Access(); + auto vertex = this->CreateVertex(acc1.get()); + ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); + ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10))); + ASSERT_NO_ERROR(acc1->Commit()); + + auto *tx_db = disk_label_property_index->GetRocksDBStorage()->db_; + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc2 = this->storage->Access(std::nullopt); + auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue()); + ASSERT_FALSE(acc2->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + + auto acc3 = this->storage->Access(std::nullopt); + auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); + ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue()); + ASSERT_FALSE(acc3->Commit().HasError()); + + ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); + } +} diff --git a/tests/unit/storage_v2_isolation_level.cpp b/tests/unit/storage_v2_isolation_level.cpp index dc0bb0d62..5cdfb5656 100644 --- a/tests/unit/storage_v2_isolation_level.cpp +++ b/tests/unit/storage_v2_isolation_level.cpp @@ -11,13 +11,15 @@ #include <gtest/gtest.h> +#include "disk_test_utils.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" -#include "storage/v2/storage.hpp" namespace { -int64_t VerticesCount(memgraph::storage::Storage::Accessor &accessor) { +int64_t VerticesCount(memgraph::storage::Storage::Accessor *accessor) { int64_t count{0}; - for ([[maybe_unused]] const auto &vertex : accessor.Vertices(memgraph::storage::View::NEW)) { + for ([[maybe_unused]] const auto &vertex : accessor->Vertices(memgraph::storage::View::NEW)) { ++count; } @@ -37,20 +39,16 @@ class StorageIsolationLevelTest : public ::testing::TestWithParam<memgraph::stor return std::string(IsolationLevelToString(static_cast<memgraph::storage::IsolationLevel>(info.param))); } }; -}; -TEST_P(StorageIsolationLevelTest, Visibility) { - const auto default_isolation_level = GetParam(); + void TestVisibility(std::unique_ptr<memgraph::storage::Storage> &storage, + const memgraph::storage::IsolationLevel &default_isolation_level, + const memgraph::storage::IsolationLevel &override_isolation_level) { + auto creator = storage->Access(); + auto default_isolation_level_reader = storage->Access(); + auto override_isolation_level_reader = storage->Access(override_isolation_level); - for (const auto override_isolation_level : isolation_levels) { - memgraph::storage::Storage storage{ - memgraph::storage::Config{.transaction = {.isolation_level = default_isolation_level}}}; - auto creator = storage.Access(); - auto default_isolation_level_reader = storage.Access(); - auto override_isolation_level_reader = storage.Access(override_isolation_level); - - ASSERT_EQ(VerticesCount(default_isolation_level_reader), 0); - ASSERT_EQ(VerticesCount(override_isolation_level_reader), 0); + ASSERT_EQ(VerticesCount(default_isolation_level_reader.get()), 0); + ASSERT_EQ(VerticesCount(override_isolation_level_reader.get()), 0); static constexpr auto iteration_count = 10; { @@ -59,18 +57,18 @@ TEST_P(StorageIsolationLevelTest, Visibility) { "(default isolation level = {}, override isolation level = {})", IsolationLevelToString(default_isolation_level), IsolationLevelToString(override_isolation_level))); for (size_t i = 1; i <= iteration_count; ++i) { - creator.CreateVertex(); + creator->CreateVertex(); const auto check_vertices_count = [i](auto &accessor, const auto isolation_level) { const auto expected_count = isolation_level == memgraph::storage::IsolationLevel::READ_UNCOMMITTED ? i : 0; - EXPECT_EQ(VerticesCount(accessor), expected_count); + EXPECT_EQ(VerticesCount(accessor.get()), expected_count); }; check_vertices_count(default_isolation_level_reader, default_isolation_level); check_vertices_count(override_isolation_level_reader, override_isolation_level); } } - ASSERT_FALSE(creator.Commit().HasError()); + ASSERT_FALSE(creator->Commit().HasError()); { SCOPED_TRACE(fmt::format( "Visibility after the creator transaction is committed " @@ -79,20 +77,53 @@ TEST_P(StorageIsolationLevelTest, Visibility) { const auto check_vertices_count = [](auto &accessor, const auto isolation_level) { const auto expected_count = isolation_level == memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION ? 0 : iteration_count; - ASSERT_EQ(VerticesCount(accessor), expected_count); + ASSERT_EQ(VerticesCount(accessor.get()), expected_count); }; check_vertices_count(default_isolation_level_reader, default_isolation_level); check_vertices_count(override_isolation_level_reader, override_isolation_level); } - ASSERT_FALSE(default_isolation_level_reader.Commit().HasError()); - ASSERT_FALSE(override_isolation_level_reader.Commit().HasError()); + ASSERT_FALSE(default_isolation_level_reader->Commit().HasError()); + ASSERT_FALSE(override_isolation_level_reader->Commit().HasError()); SCOPED_TRACE("Visibility after a new transaction is started"); - auto verifier = storage.Access(); - ASSERT_EQ(VerticesCount(verifier), iteration_count); - ASSERT_FALSE(verifier.Commit().HasError()); + auto verifier = storage->Access(); + ASSERT_EQ(VerticesCount(verifier.get()), iteration_count); + ASSERT_FALSE(verifier->Commit().HasError()); + } +}; + +TEST_P(StorageIsolationLevelTest, VisibilityInMemoryStorage) { + const auto default_isolation_level = GetParam(); + + for (const auto override_isolation_level : isolation_levels) { + std::unique_ptr<memgraph::storage::Storage> storage(new memgraph::storage::InMemoryStorage( + {memgraph::storage::Config{.transaction = {.isolation_level = default_isolation_level}}})); + this->TestVisibility(storage, default_isolation_level, override_isolation_level); + } +} + +TEST_P(StorageIsolationLevelTest, VisibilityOnDiskStorage) { + const auto default_isolation_level = GetParam(); + + const std::string testSuite = "storage_v2_isolation_level"; + auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); + config.transaction.isolation_level = default_isolation_level; + + for (const auto override_isolation_level : isolation_levels) { + std::unique_ptr<memgraph::storage::Storage> storage(new memgraph::storage::DiskStorage(config)); + try { + this->TestVisibility(storage, default_isolation_level, override_isolation_level); + } catch (memgraph::utils::NotYetImplemented &) { + if (default_isolation_level != memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION || + override_isolation_level != memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION) { + continue; + } + disk_test_utils::RemoveRocksDbDirs(testSuite); + throw; + } + disk_test_utils::RemoveRocksDbDirs(testSuite); } } diff --git a/tests/unit/storage_v2_property_store.cpp b/tests/unit/storage_v2_property_store.cpp index c803bfbc4..9da503f71 100644 --- a/tests/unit/storage_v2_property_store.cpp +++ b/tests/unit/storage_v2_property_store.cpp @@ -689,3 +689,41 @@ TEST(PropertyStore, SetMultipleProperties) { EXPECT_FALSE(store.InitProperties(data)); } } + +TEST(PropertyStore, HasAllProperties) { + const std::vector<std::pair<memgraph::storage::PropertyId, memgraph::storage::PropertyValue>> data{ + {memgraph::storage::PropertyId::FromInt(1), memgraph::storage::PropertyValue(true)}, + {memgraph::storage::PropertyId::FromInt(2), memgraph::storage::PropertyValue(123)}, + {memgraph::storage::PropertyId::FromInt(3), memgraph::storage::PropertyValue("three")}, + {memgraph::storage::PropertyId::FromInt(5), memgraph::storage::PropertyValue("0.0")}}; + + memgraph::storage::PropertyStore store; + EXPECT_TRUE(store.InitProperties(data)); + EXPECT_TRUE( + store.HasAllProperties({memgraph::storage::PropertyId::FromInt(1), memgraph::storage::PropertyId::FromInt(2), + memgraph::storage::PropertyId::FromInt(3)})); +} + +TEST(PropertyStore, HasAllPropertyValues) { + const std::vector<std::pair<memgraph::storage::PropertyId, memgraph::storage::PropertyValue>> data{ + {memgraph::storage::PropertyId::FromInt(1), memgraph::storage::PropertyValue(true)}, + {memgraph::storage::PropertyId::FromInt(2), memgraph::storage::PropertyValue(123)}, + {memgraph::storage::PropertyId::FromInt(3), memgraph::storage::PropertyValue("three")}, + {memgraph::storage::PropertyId::FromInt(5), memgraph::storage::PropertyValue(0.0)}}; + + memgraph::storage::PropertyStore store; + EXPECT_TRUE(store.InitProperties(data)); + EXPECT_TRUE(store.HasAllPropertyValues({memgraph::storage::PropertyValue(0.0), memgraph::storage::PropertyValue(123), + memgraph::storage::PropertyValue("three")})); +} + +TEST(PropertyStore, HasAnyProperties) { + const std::vector<std::pair<memgraph::storage::PropertyId, memgraph::storage::PropertyValue>> data{ + {memgraph::storage::PropertyId::FromInt(3), memgraph::storage::PropertyValue("three")}, + {memgraph::storage::PropertyId::FromInt(5), memgraph::storage::PropertyValue("0.0")}}; + + memgraph::storage::PropertyStore store; + EXPECT_TRUE(store.InitProperties(data)); + EXPECT_FALSE(store.HasAllPropertyValues({memgraph::storage::PropertyValue(0.0), memgraph::storage::PropertyValue(123), + memgraph::storage::PropertyValue("three")})); +} diff --git a/tests/unit/storage_v2_replication.cpp b/tests/unit/storage_v2_replication.cpp index 54baa2a65..f61d05c67 100644 --- a/tests/unit/storage_v2_replication.cpp +++ b/tests/unit/storage_v2_replication.cpp @@ -10,6 +10,7 @@ // licenses/APL.txt. #include <chrono> +#include <memory> #include <thread> #include <fmt/format.h> @@ -17,9 +18,11 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <storage/v2/inmemory/storage.hpp> #include <storage/v2/property_value.hpp> #include <storage/v2/replication/enums.hpp> -#include <storage/v2/storage.hpp> +#include "storage/v2/replication/config.hpp" +#include "storage/v2/storage.hpp" #include "storage/v2/view.hpp" using testing::UnorderedElementsAre; @@ -51,15 +54,22 @@ class ReplicationTest : public ::testing::Test { }; TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { - memgraph::storage::Storage main_store(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store = + std::make_unique<memgraph::storage::InMemoryStorage>(configuration); + std::unique_ptr<memgraph::storage::Storage> replica_store = + std::make_unique<memgraph::storage::InMemoryStorage>(configuration); - memgraph::storage::Storage replica_store(configuration); - replica_store.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(replica_store.get()); - ASSERT_FALSE(main_store - .RegisterReplica("REPLICA", memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + replica_mem_store->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); + + ASSERT_FALSE(main_mem_store + ->RegisterReplica("REPLICA", memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); // vertex create @@ -70,68 +80,68 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { const auto *vertex_property_value = "vertex_property_value"; std::optional<memgraph::storage::Gid> vertex_gid; { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_TRUE(v.AddLabel(main_store.NameToLabel(vertex_label)).HasValue()); - ASSERT_TRUE(v.SetProperty(main_store.NameToProperty(vertex_property), + ASSERT_TRUE(v.AddLabel(main_store->NameToLabel(vertex_label)).HasValue()); + ASSERT_TRUE(v.SetProperty(main_store->NameToProperty(vertex_property), memgraph::storage::PropertyValue(vertex_property_value)) .HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); ASSERT_EQ(labels->size(), 1); - ASSERT_THAT(*labels, UnorderedElementsAre(replica_store.NameToLabel(vertex_label))); + ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label))); const auto properties = v->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(properties.HasValue()); ASSERT_EQ(properties->size(), 1); ASSERT_THAT(*properties, - UnorderedElementsAre(std::make_pair(replica_store.NameToProperty(vertex_property), + UnorderedElementsAre(std::make_pair(replica_store->NameToProperty(vertex_property), memgraph::storage::PropertyValue(vertex_property_value)))); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // vertex remove label { - auto acc = main_store.Access(); - auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = main_store->Access(); + auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_TRUE(v->RemoveLabel(main_store.NameToLabel(vertex_label)).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(v->RemoveLabel(main_store->NameToLabel(vertex_label)).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); ASSERT_EQ(labels->size(), 0); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // vertex delete { - auto acc = main_store.Access(); - auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = main_store->Access(); + auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_TRUE(acc.DeleteVertex(&*v).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(acc->DeleteVertex(&*v).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_FALSE(v); vertex_gid.reset(); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // edge create @@ -141,16 +151,17 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { const auto *edge_property_value = "edge_property_value"; std::optional<memgraph::storage::Gid> edge_gid; { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - auto edge = acc.CreateEdge(&v, &v, main_store.NameToEdgeType(edge_type)); - ASSERT_TRUE(edge.HasValue()); - ASSERT_TRUE(edge->SetProperty(main_store.NameToProperty(edge_property), - memgraph::storage::PropertyValue(edge_property_value)) + auto edgeRes = acc->CreateEdge(&v, &v, main_store->NameToEdgeType(edge_type)); + ASSERT_TRUE(edgeRes.HasValue()); + auto edge = edgeRes.GetValue(); + ASSERT_TRUE(edge.SetProperty(main_store->NameToProperty(edge_property), + memgraph::storage::PropertyValue(edge_property_value)) .HasValue()); - edge_gid.emplace(edge->Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + edge_gid.emplace(edge.Gid()); + ASSERT_FALSE(acc->Commit().HasError()); } const auto find_edge = [&](const auto &edges, @@ -164,42 +175,42 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { }; { - auto acc = replica_store.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto out_edges = v->OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); const auto edge = find_edge(*out_edges, *edge_gid); - ASSERT_EQ(edge->EdgeType(), replica_store.NameToEdgeType(edge_type)); + ASSERT_EQ(edge->EdgeType(), replica_store->NameToEdgeType(edge_type)); const auto properties = edge->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(properties.HasValue()); ASSERT_EQ(properties->size(), 1); ASSERT_THAT(*properties, - UnorderedElementsAre(std::make_pair(replica_store.NameToProperty(edge_property), + UnorderedElementsAre(std::make_pair(replica_store->NameToProperty(edge_property), memgraph::storage::PropertyValue(edge_property_value)))); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // delete edge { - auto acc = main_store.Access(); - auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = main_store->Access(); + auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); auto out_edges = v->OutEdges(memgraph::storage::View::OLD); auto edge = find_edge(*out_edges, *edge_gid); ASSERT_TRUE(edge); - ASSERT_TRUE(acc.DeleteEdge(&*edge).HasValue()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_TRUE(acc->DeleteEdge(&*edge).HasValue()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto out_edges = v->OutEdges(memgraph::storage::View::OLD); ASSERT_TRUE(out_edges.HasValue()); ASSERT_FALSE(find_edge(*out_edges, *edge_gid)); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // label index create @@ -210,30 +221,32 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { const auto *property = "property"; const auto *property_extra = "property_extra"; { - ASSERT_FALSE(main_store.CreateIndex(main_store.NameToLabel(label)).HasError()); - ASSERT_FALSE(main_store.CreateIndex(main_store.NameToLabel(label), main_store.NameToProperty(property)).HasError()); + ASSERT_FALSE(main_store->CreateIndex(main_store->NameToLabel(label)).HasError()); ASSERT_FALSE( - main_store.CreateExistenceConstraint(main_store.NameToLabel(label), main_store.NameToProperty(property)) + main_store->CreateIndex(main_store->NameToLabel(label), main_store->NameToProperty(property)).HasError()); + ASSERT_FALSE( + main_store->CreateExistenceConstraint(main_store->NameToLabel(label), main_store->NameToProperty(property), {}) .HasError()); ASSERT_FALSE(main_store - .CreateUniqueConstraint(main_store.NameToLabel(label), {main_store.NameToProperty(property), - main_store.NameToProperty(property_extra)}) + ->CreateUniqueConstraint( + main_store->NameToLabel(label), + {main_store->NameToProperty(property), main_store->NameToProperty(property_extra)}, {}) .HasError()); } { - const auto indices = replica_store.ListAllIndices(); - ASSERT_THAT(indices.label, UnorderedElementsAre(replica_store.NameToLabel(label))); - ASSERT_THAT(indices.label_property, UnorderedElementsAre(std::make_pair(replica_store.NameToLabel(label), - replica_store.NameToProperty(property)))); + const auto indices = replica_store->ListAllIndices(); + ASSERT_THAT(indices.label, UnorderedElementsAre(replica_store->NameToLabel(label))); + ASSERT_THAT(indices.label_property, UnorderedElementsAre(std::make_pair(replica_store->NameToLabel(label), + replica_store->NameToProperty(property)))); - const auto constraints = replica_store.ListAllConstraints(); - ASSERT_THAT(constraints.existence, UnorderedElementsAre(std::make_pair(replica_store.NameToLabel(label), - replica_store.NameToProperty(property)))); + const auto constraints = replica_store->ListAllConstraints(); + ASSERT_THAT(constraints.existence, UnorderedElementsAre(std::make_pair(replica_store->NameToLabel(label), + replica_store->NameToProperty(property)))); ASSERT_THAT(constraints.unique, UnorderedElementsAre(std::make_pair( - replica_store.NameToLabel(label), - std::set{replica_store.NameToProperty(property), replica_store.NameToProperty(property_extra)}))); + replica_store->NameToLabel(label), + std::set{replica_store->NameToProperty(property), replica_store->NameToProperty(property_extra)}))); } // label index drop @@ -241,58 +254,69 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { // existence constraint drop // unique constriant drop { - ASSERT_FALSE(main_store.DropIndex(main_store.NameToLabel(label)).HasError()); - ASSERT_FALSE(main_store.DropIndex(main_store.NameToLabel(label), main_store.NameToProperty(property)).HasError()); - ASSERT_FALSE(main_store.DropExistenceConstraint(main_store.NameToLabel(label), main_store.NameToProperty(property)) - .HasError()); + ASSERT_FALSE(main_store->DropIndex(main_store->NameToLabel(label)).HasError()); + ASSERT_FALSE( + main_store->DropIndex(main_store->NameToLabel(label), main_store->NameToProperty(property)).HasError()); + ASSERT_FALSE( + main_store->DropExistenceConstraint(main_store->NameToLabel(label), main_store->NameToProperty(property), {}) + .HasError()); ASSERT_EQ(main_store - .DropUniqueConstraint(main_store.NameToLabel(label), {main_store.NameToProperty(property), - main_store.NameToProperty(property_extra)}) + ->DropUniqueConstraint( + main_store->NameToLabel(label), + {main_store->NameToProperty(property), main_store->NameToProperty(property_extra)}, {}) .GetValue(), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); } { - const auto indices = replica_store.ListAllIndices(); + const auto indices = replica_store->ListAllIndices(); ASSERT_EQ(indices.label.size(), 0); ASSERT_EQ(indices.label_property.size(), 0); - const auto constraints = replica_store.ListAllConstraints(); + const auto constraints = replica_store->ListAllConstraints(); ASSERT_EQ(constraints.existence.size(), 0); ASSERT_EQ(constraints.unique.size(), 0); } } TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) { - memgraph::storage::Storage main_store( + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage( {.durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}); + }})}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); - memgraph::storage::Storage replica_store1( + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage( {.durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}); - replica_store1.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); + }})}; + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); - memgraph::storage::Storage replica_store2( + replica_mem_store1->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); + + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage( {.durability = { .storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}); - replica_store2.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}); + }})}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); + replica_mem_store2->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationServerConfig{}); - ASSERT_FALSE(main_store - .RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); - ASSERT_FALSE(main_store - .RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); const auto *vertex_label = "label"; @@ -300,51 +324,51 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) { const auto *vertex_property_value = "property_value"; std::optional<memgraph::storage::Gid> vertex_gid; { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); - ASSERT_TRUE(v.AddLabel(main_store.NameToLabel(vertex_label)).HasValue()); - ASSERT_TRUE(v.SetProperty(main_store.NameToProperty(vertex_property), + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); + ASSERT_TRUE(v.AddLabel(main_store->NameToLabel(vertex_label)).HasValue()); + ASSERT_TRUE(v.SetProperty(main_store->NameToProperty(vertex_property), memgraph::storage::PropertyValue(vertex_property_value)) .HasValue()); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } const auto check_replica = [&](memgraph::storage::Storage *replica_store) { auto acc = replica_store->Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label))); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); }; - check_replica(&replica_store1); - check_replica(&replica_store2); + check_replica(replica_store1.get()); + check_replica(replica_store2.get()); - main_store.UnregisterReplica(replicas[1]); + main_mem_store->UnregisterReplica(replicas[1]); { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // REPLICA1 should contain the new vertex { - auto acc = replica_store1.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store1->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } // REPLICA2 should not contain the new vertex { - auto acc = replica_store2.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store2->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_FALSE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } @@ -352,64 +376,65 @@ TEST_F(ReplicationTest, RecoveryProcess) { std::vector<memgraph::storage::Gid> vertex_gids; // Force the creation of snapshot { - memgraph::storage::Storage main_store( + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage( {.durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_on_exit = true, - }}); + }})}; { - auto acc = main_store.Access(); + auto acc = main_store->Access(); // Create the vertex before registering a replica - auto v = acc.CreateVertex(); + auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } { // Create second WAL - memgraph::storage::Storage main_store( - {.durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}}); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage( + {.durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}})}; // Create vertices in 2 different transactions { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } - memgraph::storage::Storage main_store( + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage( {.durability = { .storage_directory = storage_directory, .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}); + }})}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); static constexpr const auto *property_name = "property_name"; static constexpr const auto property_value = 1; { // Force the creation of current WAL file - auto acc = main_store.Access(); + auto acc = main_store->Access(); for (const auto &vertex_gid : vertex_gids) { - auto v = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto v = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); ASSERT_TRUE( - v->SetProperty(main_store.NameToProperty(property_name), memgraph::storage::PropertyValue(property_value)) + v->SetProperty(main_store->NameToProperty(property_name), memgraph::storage::PropertyValue(property_value)) .HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } std::filesystem::path replica_storage_directory{std::filesystem::temp_directory_path() / @@ -419,244 +444,271 @@ TEST_F(ReplicationTest, RecoveryProcess) { static constexpr const auto *vertex_label = "vertex_label"; { - memgraph::storage::Storage replica_store( - {.durability = { - .storage_directory = replica_storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}}); + std::unique_ptr<memgraph::storage::Storage> replica_store{new memgraph::storage::InMemoryStorage( + {.durability = {.storage_directory = replica_storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}})}; + auto *replica_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(replica_store.get()); - replica_store.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); + replica_mem_store->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); - ASSERT_FALSE(main_store - .RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); - ASSERT_EQ(main_store.GetReplicaState(replicas[0]), memgraph::storage::replication::ReplicaState::RECOVERY); + ASSERT_EQ(main_mem_store->GetReplicaState(replicas[0]), memgraph::storage::replication::ReplicaState::RECOVERY); - while (main_store.GetReplicaState(replicas[0]) != memgraph::storage::replication::ReplicaState::READY) { + while (main_mem_store->GetReplicaState(replicas[0]) != memgraph::storage::replication::ReplicaState::READY) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } { - auto acc = main_store.Access(); + auto acc = main_store->Access(); for (const auto &vertex_gid : vertex_gids) { - auto v = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto v = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_TRUE(v->AddLabel(main_store.NameToLabel(vertex_label)).HasValue()); + ASSERT_TRUE(v->AddLabel(main_store->NameToLabel(vertex_label)).HasValue()); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store.Access(); + auto acc = replica_store->Access(); for (const auto &vertex_gid : vertex_gids) { - auto v = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto v = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); - ASSERT_THAT(*labels, UnorderedElementsAre(replica_store.NameToLabel(vertex_label))); + ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label))); const auto properties = v->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(properties.HasValue()); ASSERT_THAT(*properties, - UnorderedElementsAre(std::make_pair(replica_store.NameToProperty(property_name), + UnorderedElementsAre(std::make_pair(replica_store->NameToProperty(property_name), memgraph::storage::PropertyValue(property_value)))); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } { - memgraph::storage::Storage replica_store( - {.durability = { - .storage_directory = replica_storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}}); + std::unique_ptr<memgraph::storage::Storage> replica_store{new memgraph::storage::InMemoryStorage( + {.durability = {.storage_directory = replica_storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}})}; { - auto acc = replica_store.Access(); + auto acc = replica_store->Access(); for (const auto &vertex_gid : vertex_gids) { - auto v = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto v = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(memgraph::storage::View::OLD); ASSERT_TRUE(labels.HasValue()); - ASSERT_THAT(*labels, UnorderedElementsAre(replica_store.NameToLabel(vertex_label))); + ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label))); const auto properties = v->Properties(memgraph::storage::View::OLD); ASSERT_TRUE(properties.HasValue()); ASSERT_THAT(*properties, - UnorderedElementsAre(std::make_pair(replica_store.NameToProperty(property_name), + UnorderedElementsAre(std::make_pair(replica_store->NameToProperty(property_name), memgraph::storage::PropertyValue(property_value)))); } - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } } TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) { - memgraph::storage::Storage main_store(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); - memgraph::storage::Storage replica_store_async(configuration); + std::unique_ptr<memgraph::storage::Storage> replica_store_async{ + new memgraph::storage::InMemoryStorage(configuration)}; - replica_store_async.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}); + auto *replica_mem_store_async = static_cast<memgraph::storage::InMemoryStorage *>(replica_store_async.get()); - ASSERT_FALSE(main_store - .RegisterReplica("REPLICA_ASYNC", memgraph::io::network::Endpoint{local_host, ports[1]}, - memgraph::storage::replication::ReplicationMode::ASYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + replica_mem_store_async->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationServerConfig{}); + + ASSERT_FALSE(main_mem_store + ->RegisterReplica("REPLICA_ASYNC", memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationMode::ASYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); static constexpr size_t vertices_create_num = 10; std::vector<memgraph::storage::Gid> created_vertices; for (size_t i = 0; i < vertices_create_num; ++i) { - auto acc = main_store.Access(); - auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + auto v = acc->CreateVertex(); created_vertices.push_back(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); if (i == 0) { - ASSERT_EQ(main_store.GetReplicaState("REPLICA_ASYNC"), memgraph::storage::replication::ReplicaState::REPLICATING); + ASSERT_EQ(main_mem_store->GetReplicaState("REPLICA_ASYNC"), + memgraph::storage::replication::ReplicaState::REPLICATING); } else { - ASSERT_EQ(main_store.GetReplicaState("REPLICA_ASYNC"), memgraph::storage::replication::ReplicaState::RECOVERY); + ASSERT_EQ(main_mem_store->GetReplicaState("REPLICA_ASYNC"), + memgraph::storage::replication::ReplicaState::RECOVERY); } } - while (main_store.GetReplicaState("REPLICA_ASYNC") != memgraph::storage::replication::ReplicaState::READY) { + while (main_mem_store->GetReplicaState("REPLICA_ASYNC") != memgraph::storage::replication::ReplicaState::READY) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } ASSERT_TRUE(std::all_of(created_vertices.begin(), created_vertices.end(), [&](const auto vertex_gid) { - auto acc = replica_store_async.Access(); - auto v = acc.FindVertex(vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store_async->Access(); + auto v = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); const bool exists = v.has_value(); - EXPECT_FALSE(acc.Commit().HasError()); + EXPECT_FALSE(acc->Commit().HasError()); return exists; })); } TEST_F(ReplicationTest, EpochTest) { - memgraph::storage::Storage main_store(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); - memgraph::storage::Storage replica_store1(configuration); + replica_mem_store1->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); - replica_store1.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); - memgraph::storage::Storage replica_store2(configuration); + replica_mem_store2->SetReplicaRole(memgraph::io::network::Endpoint{local_host, 10001}, + memgraph::storage::replication::ReplicationServerConfig{}); - replica_store2.SetReplicaRole(memgraph::io::network::Endpoint{local_host, 10001}); - - ASSERT_FALSE(main_store - .RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); - ASSERT_FALSE(main_store - .RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, 10001}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, 10001}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); std::optional<memgraph::storage::Gid> vertex_gid; { - auto acc = main_store.Access(); - const auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + const auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store1.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store1->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store2.Access(); - const auto v = acc.FindVertex(*vertex_gid, memgraph::storage::View::OLD); + auto acc = replica_store2->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - main_store.UnregisterReplica(replicas[0]); - main_store.UnregisterReplica(replicas[1]); + main_mem_store->UnregisterReplica(replicas[0]); + main_mem_store->UnregisterReplica(replicas[1]); + + replica_mem_store1->SetMainReplicationRole(); + ASSERT_FALSE(replica_mem_store1 + ->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, 10001}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) - replica_store1.SetMainReplicationRole(); - ASSERT_FALSE(replica_store1 - .RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, 10001}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) .HasError()); { - auto acc = main_store.Access(); - acc.CreateVertex(); - ASSERT_FALSE(acc.Commit().HasError()); + auto acc = main_store->Access(); + acc->CreateVertex(); + ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = replica_store1.Access(); - auto v = acc.CreateVertex(); + auto acc = replica_store1->Access(); + auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + 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, memgraph::storage::View::OLD); + auto acc = replica_store2->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } - replica_store1.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); - ASSERT_TRUE(main_store - .RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + replica_mem_store1->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); + ASSERT_TRUE(main_mem_store + ->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .HasError()); { - auto acc = main_store.Access(); - const auto v = acc.CreateVertex(); + auto acc = main_store->Access(); + const auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc.Commit().HasError()); + 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, memgraph::storage::View::OLD); + auto acc = replica_store1->Access(); + const auto v = acc->FindVertex(*vertex_gid, memgraph::storage::View::OLD); ASSERT_FALSE(v); - ASSERT_FALSE(acc.Commit().HasError()); + ASSERT_FALSE(acc->Commit().HasError()); } } TEST_F(ReplicationTest, ReplicationInformation) { - memgraph::storage::Storage main_store(configuration); - - memgraph::storage::Storage replica_store1(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); const memgraph::io::network::Endpoint replica1_endpoint{local_host, 10001}; - replica_store1.SetReplicaRole(replica1_endpoint); + replica_mem_store1->SetReplicaRole(replica1_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); const memgraph::io::network::Endpoint replica2_endpoint{local_host, 10002}; - memgraph::storage::Storage replica_store2(configuration); - - replica_store2.SetReplicaRole(replica2_endpoint); + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); + replica_mem_store2->SetReplicaRole(replica2_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); const std::string replica1_name{replicas[0]}; - ASSERT_FALSE(main_store - .RegisterReplica(replica1_name, replica1_endpoint, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replica1_name, replica1_endpoint, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .HasError()); const std::string replica2_name{replicas[1]}; - ASSERT_FALSE(main_store - .RegisterReplica(replica2_name, replica2_endpoint, - memgraph::storage::replication::ReplicationMode::ASYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replica2_name, replica2_endpoint, + memgraph::storage::replication::ReplicationMode::ASYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .HasError()); - ASSERT_EQ(main_store.GetReplicationRole(), memgraph::storage::replication::ReplicationRole::MAIN); - ASSERT_EQ(replica_store1.GetReplicationRole(), memgraph::storage::replication::ReplicationRole::REPLICA); - ASSERT_EQ(replica_store2.GetReplicationRole(), memgraph::storage::replication::ReplicationRole::REPLICA); + ASSERT_EQ(main_mem_store->GetReplicationRole(), memgraph::storage::replication::ReplicationRole::MAIN); + ASSERT_EQ(replica_mem_store1->GetReplicationRole(), memgraph::storage::replication::ReplicationRole::REPLICA); + ASSERT_EQ(replica_mem_store2->GetReplicationRole(), memgraph::storage::replication::ReplicationRole::REPLICA); - const auto replicas_info = main_store.ReplicasInfo(); + const auto replicas_info = main_mem_store->ReplicasInfo(); ASSERT_EQ(replicas_info.size(), 2); const auto &first_info = replicas_info[0]; @@ -673,82 +725,96 @@ TEST_F(ReplicationTest, ReplicationInformation) { } TEST_F(ReplicationTest, ReplicationReplicaWithExistingName) { - memgraph::storage::Storage main_store(configuration); - - memgraph::storage::Storage replica_store1(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); const memgraph::io::network::Endpoint replica1_endpoint{local_host, 10001}; - replica_store1.SetReplicaRole(replica1_endpoint); + replica_mem_store1->SetReplicaRole(replica1_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); const memgraph::io::network::Endpoint replica2_endpoint{local_host, 10002}; - memgraph::storage::Storage replica_store2(configuration); - - replica_store2.SetReplicaRole(replica2_endpoint); + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); + replica_mem_store2->SetReplicaRole(replica2_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); const std::string replica1_name{replicas[0]}; - ASSERT_FALSE(main_store - .RegisterReplica(replica1_name, replica1_endpoint, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replica1_name, replica1_endpoint, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); const std::string replica2_name{replicas[0]}; - ASSERT_TRUE(main_store - .RegisterReplica(replica2_name, replica2_endpoint, - memgraph::storage::replication::ReplicationMode::ASYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) - .GetError() == memgraph::storage::Storage::RegisterReplicaError::NAME_EXISTS); + ASSERT_TRUE(main_mem_store + ->RegisterReplica(replica2_name, replica2_endpoint, + memgraph::storage::replication::ReplicationMode::ASYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .GetError() == memgraph::storage::InMemoryStorage::RegisterReplicaError::NAME_EXISTS); } TEST_F(ReplicationTest, ReplicationReplicaWithExistingEndPoint) { - memgraph::storage::Storage main_store(configuration); - - memgraph::storage::Storage replica_store1(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); const memgraph::io::network::Endpoint replica1_endpoint{local_host, 10001}; - replica_store1.SetReplicaRole(replica1_endpoint); + replica_mem_store1->SetReplicaRole(replica1_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); + + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); const memgraph::io::network::Endpoint replica2_endpoint{local_host, 10001}; - memgraph::storage::Storage replica_store2(configuration); - - replica_store2.SetReplicaRole(replica2_endpoint); + replica_mem_store2->SetReplicaRole(replica2_endpoint, memgraph::storage::replication::ReplicationServerConfig{}); const std::string replica1_name{replicas[0]}; - ASSERT_FALSE(main_store - .RegisterReplica(replica1_name, replica1_endpoint, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) + ASSERT_FALSE(main_mem_store + ->RegisterReplica(replica1_name, replica1_endpoint, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) .HasError()); const std::string replica2_name{replicas[1]}; - ASSERT_TRUE(main_store - .RegisterReplica(replica2_name, replica2_endpoint, - memgraph::storage::replication::ReplicationMode::ASYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) - .GetError() == memgraph::storage::Storage::RegisterReplicaError::END_POINT_EXISTS); + ASSERT_TRUE(main_mem_store + ->RegisterReplica(replica2_name, replica2_endpoint, + memgraph::storage::replication::ReplicationMode::ASYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .GetError() == memgraph::storage::InMemoryStorage::RegisterReplicaError::END_POINT_EXISTS); } TEST_F(ReplicationTest, RestoringReplicationAtStartupAftgerDroppingReplica) { auto main_config = configuration; main_config.durability.restore_replication_state_on_startup = true; - auto main_store = std::make_unique<memgraph::storage::Storage>(main_config); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(main_config)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); - memgraph::storage::Storage replica_store1(configuration); - replica_store1.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); + replica_mem_store1->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); - memgraph::storage::Storage replica_store2(configuration); - replica_store2.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}); + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); + replica_mem_store2->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationServerConfig{}); - auto res = main_store->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID); + auto res = main_mem_store->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}); ASSERT_FALSE(res.HasError()); - res = main_store->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID); + res = main_mem_store->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}); ASSERT_FALSE(res.HasError()); - auto replica_infos = main_store->ReplicasInfo(); + auto replica_infos = main_mem_store->ReplicasInfo(); ASSERT_EQ(replica_infos.size(), 2); ASSERT_EQ(replica_infos[0].name, replicas[0]); @@ -760,8 +826,10 @@ TEST_F(ReplicationTest, RestoringReplicationAtStartupAftgerDroppingReplica) { main_store.reset(); - auto other_main_store = std::make_unique<memgraph::storage::Storage>(main_config); - replica_infos = other_main_store->ReplicasInfo(); + std::unique_ptr<memgraph::storage::Storage> other_main_store{new memgraph::storage::InMemoryStorage(main_config)}; + auto *other_main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(other_main_store.get()); + + replica_infos = other_main_mem_store->ReplicasInfo(); ASSERT_EQ(replica_infos.size(), 2); ASSERT_EQ(replica_infos[0].name, replicas[0]); ASSERT_EQ(replica_infos[0].endpoint.address, local_host); @@ -774,23 +842,33 @@ TEST_F(ReplicationTest, RestoringReplicationAtStartupAftgerDroppingReplica) { TEST_F(ReplicationTest, RestoringReplicationAtStartup) { auto main_config = configuration; main_config.durability.restore_replication_state_on_startup = true; - auto main_store = std::make_unique<memgraph::storage::Storage>(main_config); - memgraph::storage::Storage replica_store1(configuration); - replica_store1.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}); - memgraph::storage::Storage replica_store2(configuration); - replica_store2.SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(main_config)}; + std::unique_ptr<memgraph::storage::Storage> replica_store1{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); + auto *replica_mem_store1 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store1.get()); - auto res = main_store->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID); + replica_mem_store1->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationServerConfig{}); + + std::unique_ptr<memgraph::storage::Storage> replica_store2{new memgraph::storage::InMemoryStorage(configuration)}; + auto *replica_mem_store2 = static_cast<memgraph::storage::InMemoryStorage *>(replica_store2.get()); + + replica_mem_store2->SetReplicaRole(memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationServerConfig{}); + + auto res = main_mem_store->RegisterReplica(replicas[0], memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}); ASSERT_FALSE(res.HasError()); - res = main_store->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID); + res = main_mem_store->RegisterReplica(replicas[1], memgraph::io::network::Endpoint{local_host, ports[1]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}); ASSERT_FALSE(res.HasError()); - auto replica_infos = main_store->ReplicasInfo(); + auto replica_infos = main_mem_store->ReplicasInfo(); ASSERT_EQ(replica_infos.size(), 2); ASSERT_EQ(replica_infos[0].name, replicas[0]); @@ -800,10 +878,10 @@ TEST_F(ReplicationTest, RestoringReplicationAtStartup) { ASSERT_EQ(replica_infos[1].endpoint.address, local_host); ASSERT_EQ(replica_infos[1].endpoint.port, ports[1]); - const auto unregister_res = main_store->UnregisterReplica(replicas[0]); + const auto unregister_res = main_mem_store->UnregisterReplica(replicas[0]); ASSERT_TRUE(unregister_res); - replica_infos = main_store->ReplicasInfo(); + replica_infos = main_mem_store->ReplicasInfo(); ASSERT_EQ(replica_infos.size(), 1); ASSERT_EQ(replica_infos[0].name, replicas[1]); ASSERT_EQ(replica_infos[0].endpoint.address, local_host); @@ -811,8 +889,9 @@ TEST_F(ReplicationTest, RestoringReplicationAtStartup) { main_store.reset(); - auto other_main_store = std::make_unique<memgraph::storage::Storage>(main_config); - replica_infos = other_main_store->ReplicasInfo(); + std::unique_ptr<memgraph::storage::Storage> other_main_store{new memgraph::storage::InMemoryStorage(main_config)}; + auto *other_main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(other_main_store.get()); + replica_infos = other_main_mem_store->ReplicasInfo(); ASSERT_EQ(replica_infos.size(), 1); ASSERT_EQ(replica_infos[0].name, replicas[1]); ASSERT_EQ(replica_infos[0].endpoint.address, local_host); @@ -820,11 +899,13 @@ TEST_F(ReplicationTest, RestoringReplicationAtStartup) { } TEST_F(ReplicationTest, AddingInvalidReplica) { - memgraph::storage::Storage main_store(configuration); + std::unique_ptr<memgraph::storage::Storage> main_store{new memgraph::storage::InMemoryStorage(configuration)}; + auto *main_mem_store = static_cast<memgraph::storage::InMemoryStorage *>(main_store.get()); - ASSERT_TRUE(main_store - .RegisterReplica("REPLICA", memgraph::io::network::Endpoint{local_host, ports[0]}, - memgraph::storage::replication::ReplicationMode::SYNC, - memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID) - .GetError() == memgraph::storage::Storage::RegisterReplicaError::CONNECTION_FAILED); + ASSERT_TRUE(main_mem_store + ->RegisterReplica("REPLICA", memgraph::io::network::Endpoint{local_host, ports[0]}, + memgraph::storage::replication::ReplicationMode::SYNC, + memgraph::storage::replication::RegistrationMode::MUST_BE_INSTANTLY_VALID, + memgraph::storage::replication::ReplicationClientConfig{}) + .GetError() == memgraph::storage::InMemoryStorage::RegisterReplicaError::CONNECTION_FAILED); } diff --git a/tests/unit/storage_v2_storage_mode.cpp b/tests/unit/storage_v2_storage_mode.cpp index a82d0b21a..ad0def8ce 100644 --- a/tests/unit/storage_v2_storage_mode.cpp +++ b/tests/unit/storage_v2_storage_mode.cpp @@ -18,11 +18,12 @@ #include "interpreter_faker.hpp" #include "query/exceptions.hpp" +#include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" -#include "storage/v2/storage.hpp" #include "storage/v2/storage_mode.hpp" #include "storage/v2/vertex_accessor.hpp" #include "storage_test_utils.hpp" +#include "utils/exceptions.hpp" class StorageModeTest : public ::testing::TestWithParam<memgraph::storage::StorageMode> { public: @@ -37,27 +38,29 @@ class StorageModeTest : public ::testing::TestWithParam<memgraph::storage::Stora TEST_P(StorageModeTest, Mode) { const memgraph::storage::StorageMode storage_mode = GetParam(); - memgraph::storage::Storage storage{ - {.transaction{.isolation_level = memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION}}}; - storage.SetStorageMode(storage_mode); - auto creator = storage.Access(); - auto other_analytics_mode_reader = storage.Access(); + std::unique_ptr<memgraph::storage::Storage> storage = + std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{ + .transaction{.isolation_level = memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION}}); - ASSERT_EQ(CountVertices(creator, memgraph::storage::View::OLD), 0); - ASSERT_EQ(CountVertices(other_analytics_mode_reader, memgraph::storage::View::OLD), 0); + storage->SetStorageMode(storage_mode); + auto creator = storage->Access(); + auto other_analytics_mode_reader = storage->Access(); + + ASSERT_EQ(CountVertices(*creator, memgraph::storage::View::OLD), 0); + ASSERT_EQ(CountVertices(*other_analytics_mode_reader, memgraph::storage::View::OLD), 0); static constexpr int vertex_creation_count = 10; { for (size_t i = 1; i <= vertex_creation_count; i++) { - creator.CreateVertex(); + creator->CreateVertex(); int64_t expected_vertices_count = storage_mode == memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL ? i : 0; - ASSERT_EQ(CountVertices(creator, memgraph::storage::View::OLD), expected_vertices_count); - ASSERT_EQ(CountVertices(other_analytics_mode_reader, memgraph::storage::View::OLD), expected_vertices_count); + ASSERT_EQ(CountVertices(*creator, memgraph::storage::View::OLD), expected_vertices_count); + ASSERT_EQ(CountVertices(*other_analytics_mode_reader, memgraph::storage::View::OLD), expected_vertices_count); } } - ASSERT_FALSE(creator.Commit().HasError()); + ASSERT_FALSE(creator->Commit().HasError()); } INSTANTIATE_TEST_CASE_P(ParameterizedStorageModeTests, StorageModeTest, ::testing::ValuesIn(storage_modes), @@ -65,9 +68,9 @@ INSTANTIATE_TEST_CASE_P(ParameterizedStorageModeTests, StorageModeTest, ::testin class StorageModeMultiTxTest : public ::testing::Test { protected: - memgraph::storage::Storage db_; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_storage_mode"}; - memgraph::query::InterpreterContext interpreter_context{&db_, {}, data_directory}; + memgraph::query::InterpreterContext interpreter_context{ + std::make_unique<memgraph::storage::InMemoryStorage>(), {}, data_directory}; InterpreterFaker running_interpreter{&interpreter_context}, main_interpreter{&interpreter_context}; }; @@ -84,11 +87,11 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchInactiveTransaction) { while (!started) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); main_interpreter.Interpret("STORAGE MODE IN_MEMORY_ANALYTICAL"); // should change state - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); // finish thread running_thread.request_stop(); @@ -97,7 +100,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchInactiveTransaction) { TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) { // transactional state - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); main_interpreter.Interpret("BEGIN"); bool started = false; @@ -116,7 +119,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } // should not change still - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); main_interpreter.Interpret("COMMIT"); @@ -124,7 +127,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } // should change state - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); // finish thread running_thread.request_stop(); @@ -132,11 +135,11 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) { } TEST_F(StorageModeMultiTxTest, ErrorChangeIsolationLevel) { - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL); main_interpreter.Interpret("STORAGE MODE IN_MEMORY_ANALYTICAL"); // should change state - ASSERT_EQ(db_.GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); + ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL); ASSERT_THROW(running_interpreter.Interpret("SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;"), memgraph::query::IsolationLevelModificationInAnalyticsException); diff --git a/tests/unit/transaction_queue.cpp b/tests/unit/transaction_queue.cpp index 812f9cb2b..d04006a32 100644 --- a/tests/unit/transaction_queue.cpp +++ b/tests/unit/transaction_queue.cpp @@ -17,25 +17,34 @@ #include <gtest/gtest.h> #include "gmock/gmock.h" +#include "disk_test_utils.hpp" #include "interpreter_faker.hpp" +#include "storage/v2/inmemory/storage.hpp" /* Tests rely on the fact that interpreters are sequentially added to runninng_interpreters to get transaction_id of its corresponding interpreter/. */ +template <typename StorageType> class TransactionQueueSimpleTest : public ::testing::Test { protected: - memgraph::storage::Storage db_; + const std::string testSuite = "transactin_queue"; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_transaction_queue_intr"}; - memgraph::query::InterpreterContext interpreter_context{&db_, {}, data_directory}; + memgraph::query::InterpreterContext interpreter_context{ + std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {}, data_directory}; InterpreterFaker running_interpreter{&interpreter_context}, main_interpreter{&interpreter_context}; + + void TearDown() override { disk_test_utils::RemoveRocksDbDirs(testSuite); } }; -TEST_F(TransactionQueueSimpleTest, TwoInterpretersInterleaving) { +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(TransactionQueueSimpleTest, StorageTypes); + +TYPED_TEST(TransactionQueueSimpleTest, TwoInterpretersInterleaving) { bool started = false; std::jthread running_thread = std::jthread( [this, &started](std::stop_token st, int thread_index) { - running_interpreter.Interpret("BEGIN"); + this->running_interpreter.Interpret("BEGIN"); started = true; }, 0); @@ -44,8 +53,8 @@ TEST_F(TransactionQueueSimpleTest, TwoInterpretersInterleaving) { while (!started) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - main_interpreter.Interpret("CREATE (:Person {prop: 1})"); - auto show_stream = main_interpreter.Interpret("SHOW TRANSACTIONS"); + this->main_interpreter.Interpret("CREATE (:Person {prop: 1})"); + auto show_stream = this->main_interpreter.Interpret("SHOW TRANSACTIONS"); ASSERT_EQ(show_stream.GetResults().size(), 2U); // superadmin executing the transaction EXPECT_EQ(show_stream.GetResults()[0][0].ValueString(), ""); @@ -57,18 +66,18 @@ TEST_F(TransactionQueueSimpleTest, TwoInterpretersInterleaving) { // Kill the other transaction std::string run_trans_id = show_stream.GetResults()[1][1].ValueString(); std::string esc_run_trans_id = "'" + run_trans_id + "'"; - auto terminate_stream = main_interpreter.Interpret("TERMINATE TRANSACTIONS " + esc_run_trans_id); + auto terminate_stream = this->main_interpreter.Interpret("TERMINATE TRANSACTIONS " + esc_run_trans_id); // check result of killing ASSERT_EQ(terminate_stream.GetResults().size(), 1U); EXPECT_EQ(terminate_stream.GetResults()[0][0].ValueString(), run_trans_id); ASSERT_TRUE(terminate_stream.GetResults()[0][1].ValueBool()); // that the transaction is actually killed // check the number of transactions now - auto show_stream_after_killing = main_interpreter.Interpret("SHOW TRANSACTIONS"); + auto show_stream_after_killing = this->main_interpreter.Interpret("SHOW TRANSACTIONS"); ASSERT_EQ(show_stream_after_killing.GetResults().size(), 1U); // test the state of the database - auto results_stream = main_interpreter.Interpret("MATCH (n) RETURN n"); + auto results_stream = this->main_interpreter.Interpret("MATCH (n) RETURN n"); ASSERT_EQ(results_stream.GetResults().size(), 1U); // from the main interpreter - main_interpreter.Interpret("MATCH (n) DETACH DELETE n"); + this->main_interpreter.Interpret("MATCH (n) DETACH DELETE n"); // finish thread running_thread.request_stop(); } diff --git a/tests/unit/transaction_queue_multiple.cpp b/tests/unit/transaction_queue_multiple.cpp index 64cd10ad8..c2feb89aa 100644 --- a/tests/unit/transaction_queue_multiple.cpp +++ b/tests/unit/transaction_queue_multiple.cpp @@ -19,8 +19,12 @@ #include "gmock/gmock.h" #include "spdlog/spdlog.h" +#include "disk_test_utils.hpp" #include "interpreter_faker.hpp" #include "query/exceptions.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" constexpr int NUM_INTERPRETERS = 4, INSERTIONS = 4000; @@ -28,12 +32,14 @@ constexpr int NUM_INTERPRETERS = 4, INSERTIONS = 4000; Tests rely on the fact that interpreters are sequentially added to running_interpreters to get transaction_id of its corresponding interpreter. */ +template <typename StorageType> class TransactionQueueMultipleTest : public ::testing::Test { protected: - memgraph::storage::Storage db_; + const std::string testSuite = "transactin_queue_multiple"; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_transaction_queue_multiple_intr"}; - memgraph::query::InterpreterContext interpreter_context{&db_, {}, data_directory}; + memgraph::query::InterpreterContext interpreter_context{ + std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {}, data_directory}; InterpreterFaker main_interpreter{&interpreter_context}; std::vector<InterpreterFaker *> running_interpreters; @@ -48,19 +54,24 @@ class TransactionQueueMultipleTest : public ::testing::Test { for (int i = 0; i < NUM_INTERPRETERS; ++i) { delete running_interpreters[i]; } + disk_test_utils::RemoveRocksDbDirs(testSuite); } }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(TransactionQueueMultipleTest, StorageTypes); + // Tests whether admin can see transaction of superadmin -TEST_F(TransactionQueueMultipleTest, TerminateTransaction) { +TYPED_TEST(TransactionQueueMultipleTest, TerminateTransaction) { std::vector<bool> started(NUM_INTERPRETERS, false); auto thread_func = [this, &started](int thread_index) { try { - running_interpreters[thread_index]->Interpret("BEGIN"); + this->running_interpreters[thread_index]->Interpret("BEGIN"); started[thread_index] = true; // add try-catch block for (int j = 0; j < INSERTIONS; ++j) { - running_interpreters[thread_index]->Interpret("CREATE (:Person {prop: " + std::to_string(thread_index) + "})"); + this->running_interpreters[thread_index]->Interpret("CREATE (:Person {prop: " + std::to_string(thread_index) + + "})"); } } catch (memgraph::query::HintedAbortError &e) { } @@ -77,7 +88,7 @@ TEST_F(TransactionQueueMultipleTest, TerminateTransaction) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } - auto show_stream = main_interpreter.Interpret("SHOW TRANSACTIONS"); + auto show_stream = this->main_interpreter.Interpret("SHOW TRANSACTIONS"); ASSERT_EQ(show_stream.GetResults().size(), NUM_INTERPRETERS + 1); // Choose random transaction to kill std::random_device rd; @@ -86,15 +97,15 @@ TEST_F(TransactionQueueMultipleTest, TerminateTransaction) { int index_to_terminate = distr(gen); // Kill random transaction std::string run_trans_id = - std::to_string(running_interpreters[index_to_terminate]->interpreter.GetTransactionId().value()); + std::to_string(this->running_interpreters[index_to_terminate]->interpreter.GetTransactionId().value()); std::string esc_run_trans_id = "'" + run_trans_id + "'"; - auto terminate_stream = main_interpreter.Interpret("TERMINATE TRANSACTIONS " + esc_run_trans_id); + auto terminate_stream = this->main_interpreter.Interpret("TERMINATE TRANSACTIONS " + esc_run_trans_id); // check result of killing ASSERT_EQ(terminate_stream.GetResults().size(), 1U); EXPECT_EQ(terminate_stream.GetResults()[0][0].ValueString(), run_trans_id); ASSERT_TRUE(terminate_stream.GetResults()[0][1].ValueBool()); // that the transaction is actually killed // test here show transactions - auto show_stream_after_kill = main_interpreter.Interpret("SHOW TRANSACTIONS"); + auto show_stream_after_kill = this->main_interpreter.Interpret("SHOW TRANSACTIONS"); ASSERT_EQ(show_stream_after_kill.GetResults().size(), NUM_INTERPRETERS); // wait to finish for threads for (int i = 0; i < NUM_INTERPRETERS; ++i) { @@ -103,16 +114,16 @@ TEST_F(TransactionQueueMultipleTest, TerminateTransaction) { // test the state of the database for (int i = 0; i < NUM_INTERPRETERS; ++i) { if (i != index_to_terminate) { - running_interpreters[i]->Interpret("COMMIT"); + this->running_interpreters[i]->Interpret("COMMIT"); } std::string fetch_query = "MATCH (n:Person) WHERE n.prop=" + std::to_string(i) + " RETURN n"; - auto results_stream = main_interpreter.Interpret(fetch_query); + auto results_stream = this->main_interpreter.Interpret(fetch_query); if (i == index_to_terminate) { ASSERT_EQ(results_stream.GetResults().size(), 0); } else { ASSERT_EQ(results_stream.GetResults().size(), INSERTIONS); } } - main_interpreter.Interpret("MATCH (n) DETACH DELETE n"); + this->main_interpreter.Interpret("MATCH (n) DETACH DELETE n"); } } diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp index 0238363b6..5f2f8f3bf 100644 --- a/tests/unit/typed_value.cpp +++ b/tests/unit/typed_value.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -20,19 +20,25 @@ #include "gtest/gtest.h" +#include "disk_test_utils.hpp" #include "query/graph.hpp" #include "query/typed_value.hpp" -#include "storage/v2/storage.hpp" +#include "storage/v2/disk/storage.hpp" +#include "storage/v2/inmemory/storage.hpp" using memgraph::query::TypedValue; using memgraph::query::TypedValueException; +template <typename StorageType> class AllTypesFixture : public testing::Test { protected: + const std::string testSuite = "typed_value"; + std::vector<TypedValue> values_; - memgraph::storage::Storage db; - memgraph::storage::Storage::Accessor storage_dba{db.Access()}; - memgraph::query::DbAccessor dba{&storage_dba}; + memgraph::storage::Config config_{disk_test_utils::GenerateOnDiskConfig(testSuite)}; + std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config_)}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + memgraph::query::DbAccessor dba{storage_dba.get()}; void SetUp() override { values_.emplace_back(TypedValue()); @@ -49,16 +55,21 @@ class AllTypesFixture : public testing::Test { {"e", TypedValue()}}); auto vertex = dba.InsertVertex(); values_.emplace_back(vertex); - auto edge = *dba.InsertEdge(&vertex, &vertex, dba.NameToEdgeType("et")); - values_.emplace_back(edge); + auto edge = dba.InsertEdge(&vertex, &vertex, dba.NameToEdgeType("et")); + values_.emplace_back(*edge); values_.emplace_back(memgraph::query::Path(dba.InsertVertex())); memgraph::query::Graph graph{memgraph::utils::NewDeleteResource()}; graph.InsertVertex(vertex); - graph.InsertEdge(edge); + graph.InsertEdge(*edge); values_.emplace_back(std::move(graph)); } + + void TearDown() override { disk_test_utils::RemoveRocksDbDirs(testSuite); } }; +using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>; +TYPED_TEST_CASE(AllTypesFixture, StorageTypes); + void EXPECT_PROP_FALSE(const TypedValue &a) { EXPECT_TRUE(a.type() == TypedValue::Type::Bool && !a.ValueBool()); } void EXPECT_PROP_TRUE(const TypedValue &a) { EXPECT_TRUE(a.type() == TypedValue::Type::Bool && a.ValueBool()); } @@ -168,12 +179,12 @@ TEST(TypedValue, Hash) { hash(TypedValue(std::map<std::string, TypedValue>{{"a", TypedValue(1)}}))); } -TEST_F(AllTypesFixture, Less) { +TYPED_TEST(AllTypesFixture, Less) { // 'Less' is legal only between numerics, Null and strings. auto is_string_compatible = [](const TypedValue &v) { return v.IsNull() || v.type() == TypedValue::Type::String; }; auto is_numeric_compatible = [](const TypedValue &v) { return v.IsNull() || v.IsNumeric(); }; - for (TypedValue &a : values_) { - for (TypedValue &b : values_) { + for (TypedValue &a : this->values_) { + for (TypedValue &b : this->values_) { if (is_numeric_compatible(a) && is_numeric_compatible(b)) continue; if (is_string_compatible(a) && is_string_compatible(b)) continue; // Comparison should raise an exception. Cast to (void) so the compiler @@ -183,7 +194,7 @@ TEST_F(AllTypesFixture, Less) { } // legal_type < Null = Null - for (TypedValue &value : values_) { + for (TypedValue &value : this->values_) { if (!(value.IsNumeric() || value.type() == TypedValue::Type::String)) continue; EXPECT_PROP_ISNULL(value < TypedValue()); EXPECT_PROP_ISNULL(TypedValue() < value); @@ -239,7 +250,8 @@ TEST(TypedValue, UnaryPlus) { EXPECT_THROW(+TypedValue("something"), TypedValueException); } -class TypedValueArithmeticTest : public AllTypesFixture { +template <typename StorageType> +class TypedValueArithmeticTest : public AllTypesFixture<StorageType> { protected: /** * Performs a series of tests on properties of all types. The tests @@ -271,8 +283,8 @@ class TypedValueArithmeticTest : public AllTypesFixture { } }; - for (const TypedValue &a : values_) { - for (const TypedValue &b : values_) { + for (const TypedValue &a : this->values_) { + for (const TypedValue &b : this->values_) { if (always_valid(a) || always_valid(b)) continue; if (valid(a) && valid(b)) continue; EXPECT_THROW(op(a, b), TypedValueException); @@ -281,15 +293,17 @@ class TypedValueArithmeticTest : public AllTypesFixture { } // null resulting ops - for (const TypedValue &value : values_) { + for (const TypedValue &value : this->values_) { EXPECT_PROP_ISNULL(op(value, TypedValue())); EXPECT_PROP_ISNULL(op(TypedValue(), value)); } } }; -TEST_F(TypedValueArithmeticTest, Sum) { - ExpectArithmeticThrowsAndNull(true, [](const TypedValue &a, const TypedValue &b) { return a + b; }); +TYPED_TEST_CASE(TypedValueArithmeticTest, StorageTypes); + +TYPED_TEST(TypedValueArithmeticTest, Sum) { + this->ExpectArithmeticThrowsAndNull(true, [](const TypedValue &a, const TypedValue &b) { return a + b; }); // sum of props of the same type EXPECT_EQ((TypedValue(2) + TypedValue(3)).ValueInt(), 5); @@ -329,8 +343,8 @@ TEST_F(TypedValueArithmeticTest, Sum) { TypedValueException); } -TEST_F(TypedValueArithmeticTest, Difference) { - ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a - b; }); +TYPED_TEST(TypedValueArithmeticTest, Difference) { + this->ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a - b; }); // difference of props of the same type EXPECT_EQ((TypedValue(2) - TypedValue(3)).ValueInt(), -1); @@ -358,10 +372,10 @@ TEST_F(TypedValueArithmeticTest, Difference) { TypedValueException); } -TEST_F(TypedValueArithmeticTest, Negate) { EXPECT_NO_THROW(-TypedValue(memgraph::utils::Duration(1))); } +TYPED_TEST(TypedValueArithmeticTest, Negate) { EXPECT_NO_THROW(-TypedValue(memgraph::utils::Duration(1))); } -TEST_F(TypedValueArithmeticTest, Divison) { - ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a / b; }); +TYPED_TEST(TypedValueArithmeticTest, Divison) { + this->ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a / b; }); EXPECT_THROW(TypedValue(1) / TypedValue(0), TypedValueException); EXPECT_PROP_EQ(TypedValue(10) / TypedValue(2), TypedValue(5)); @@ -374,8 +388,8 @@ TEST_F(TypedValueArithmeticTest, Divison) { EXPECT_FLOAT_EQ((TypedValue(10.0) / TypedValue(4)).ValueDouble(), 2.5); } -TEST_F(TypedValueArithmeticTest, Multiplication) { - ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a * b; }); +TYPED_TEST(TypedValueArithmeticTest, Multiplication) { + this->ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a * b; }); EXPECT_PROP_EQ(TypedValue(10) * TypedValue(2), TypedValue(20)); EXPECT_FLOAT_EQ((TypedValue(12.5) * TypedValue(6.6)).ValueDouble(), 12.5 * 6.6); @@ -383,8 +397,8 @@ TEST_F(TypedValueArithmeticTest, Multiplication) { EXPECT_FLOAT_EQ((TypedValue(10.2) * TypedValue(4)).ValueDouble(), 10.2 * 4); } -TEST_F(TypedValueArithmeticTest, Modulo) { - ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a % b; }); +TYPED_TEST(TypedValueArithmeticTest, Modulo) { + this->ExpectArithmeticThrowsAndNull(false, [](const TypedValue &a, const TypedValue &b) { return a % b; }); EXPECT_THROW(TypedValue(1) % TypedValue(0), TypedValueException); EXPECT_PROP_EQ(TypedValue(10) % TypedValue(2), TypedValue(0)); @@ -397,7 +411,8 @@ TEST_F(TypedValueArithmeticTest, Modulo) { EXPECT_FLOAT_EQ((TypedValue(10.0) % TypedValue(4)).ValueDouble(), 2.0); } -class TypedValueLogicTest : public AllTypesFixture { +template <typename StorageType> +class TypedValueLogicTest : public AllTypesFixture<StorageType> { protected: /** * Logical operations (logical and, or) are only legal on bools @@ -408,8 +423,8 @@ class TypedValueLogicTest : public AllTypesFixture { * @param op The logical operation to test. */ void TestLogicalThrows(std::function<TypedValue(const TypedValue &, const TypedValue &)> op) { - for (const auto &p1 : values_) { - for (const auto &p2 : values_) { + for (const auto &p1 : this->values_) { + for (const auto &p2 : this->values_) { // skip situations when both p1 and p2 are either bool or null auto p1_ok = p1.type() == TypedValue::Type::Bool || p1.IsNull(); auto p2_ok = p2.type() == TypedValue::Type::Bool || p2.IsNull(); @@ -421,8 +436,10 @@ class TypedValueLogicTest : public AllTypesFixture { } }; -TEST_F(TypedValueLogicTest, LogicalAnd) { - TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 && p2; }); +TYPED_TEST_CASE(TypedValueLogicTest, StorageTypes); + +TYPED_TEST(TypedValueLogicTest, LogicalAnd) { + this->TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 && p2; }); EXPECT_PROP_ISNULL(TypedValue() && TypedValue(true)); EXPECT_PROP_EQ(TypedValue() && TypedValue(false), TypedValue(false)); @@ -430,8 +447,8 @@ TEST_F(TypedValueLogicTest, LogicalAnd) { EXPECT_PROP_EQ(TypedValue(false) && TypedValue(true), TypedValue(false)); } -TEST_F(TypedValueLogicTest, LogicalOr) { - TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 || p2; }); +TYPED_TEST(TypedValueLogicTest, LogicalOr) { + this->TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 || p2; }); EXPECT_PROP_ISNULL(TypedValue() || TypedValue(false)); EXPECT_PROP_EQ(TypedValue() || TypedValue(true), TypedValue(true)); @@ -439,8 +456,8 @@ TEST_F(TypedValueLogicTest, LogicalOr) { EXPECT_PROP_EQ(TypedValue(false) || TypedValue(true), TypedValue(true)); } -TEST_F(TypedValueLogicTest, LogicalXor) { - TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 ^ p2; }); +TYPED_TEST(TypedValueLogicTest, LogicalXor) { + this->TestLogicalThrows([](const TypedValue &p1, const TypedValue &p2) { return p1 ^ p2; }); EXPECT_PROP_ISNULL(TypedValue() && TypedValue(true)); EXPECT_PROP_EQ(TypedValue(true) ^ TypedValue(true), TypedValue(false)); @@ -450,10 +467,10 @@ TEST_F(TypedValueLogicTest, LogicalXor) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(AllTypesFixture, ConstructionWithMemoryResource) { +TYPED_TEST(AllTypesFixture, ConstructionWithMemoryResource) { memgraph::utils::MonotonicBufferResource monotonic_memory(1024); std::vector<TypedValue> values_with_custom_memory; - for (const auto &value : values_) { + for (const auto &value : this->values_) { EXPECT_EQ(value.GetMemoryResource(), memgraph::utils::NewDeleteResource()); TypedValue copy_constructed_value(value, &monotonic_memory); EXPECT_EQ(copy_constructed_value.GetMemoryResource(), &monotonic_memory); @@ -464,10 +481,10 @@ TEST_F(AllTypesFixture, ConstructionWithMemoryResource) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(AllTypesFixture, AssignmentWithMemoryResource) { +TYPED_TEST(AllTypesFixture, AssignmentWithMemoryResource) { std::vector<TypedValue> values_with_default_memory; memgraph::utils::MonotonicBufferResource monotonic_memory(1024); - for (const auto &value : values_) { + for (const auto &value : this->values_) { EXPECT_EQ(value.GetMemoryResource(), memgraph::utils::NewDeleteResource()); TypedValue copy_assigned_value(&monotonic_memory); copy_assigned_value = value; @@ -480,10 +497,10 @@ TEST_F(AllTypesFixture, AssignmentWithMemoryResource) { } // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(AllTypesFixture, PropagationOfMemoryOnConstruction) { +TYPED_TEST(AllTypesFixture, PropagationOfMemoryOnConstruction) { memgraph::utils::MonotonicBufferResource monotonic_memory(1024); std::vector<TypedValue, memgraph::utils::Allocator<TypedValue>> values_with_custom_memory(&monotonic_memory); - for (const auto &value : values_) { + for (const auto &value : this->values_) { EXPECT_EQ(value.GetMemoryResource(), memgraph::utils::NewDeleteResource()); values_with_custom_memory.emplace_back(value); const auto ©_constructed_value = values_with_custom_memory.back();