memgraph/tests/unit/query_procedures_mgp_graph.cpp
Andi 78a88737f8
HA: Add automatic failover (#1646)
Co-authored-by: antoniofilipovic <filipovicantonio1998@gmail.com>
2024-01-29 15:34:00 +01:00

700 lines
31 KiB
C++

// Copyright 2024 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 <algorithm>
#include <iterator>
#include <list>
#include <memory>
#include <vector>
#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/vertex_accessor.hpp"
#include "storage/v2/view.hpp"
#include "storage_test_utils.hpp"
#include "test_utils.hpp"
#include "utils/memory.hpp"
#include "utils/variant_helpers.hpp"
using memgraph::replication_coordination_glue::ReplicationRole;
#define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR)
namespace {
struct MgpEdgeDeleter {
void operator()(mgp_edge *e) {
if (e != nullptr) {
mgp_edge_destroy(e);
}
}
};
struct MgpEdgesIteratorDeleter {
void operator()(mgp_edges_iterator *it) {
if (it != nullptr) {
mgp_edges_iterator_destroy(it);
}
}
};
struct MgpVertexDeleter {
void operator()(mgp_vertex *v) {
if (v != nullptr) {
mgp_vertex_destroy(v);
}
}
};
struct MgpVerticesIteratorDeleter {
void operator()(mgp_vertices_iterator *it) {
if (it != nullptr) {
mgp_vertices_iterator_destroy(it);
}
}
};
struct MgpValueDeleter {
void operator()(mgp_value *v) {
if (v != nullptr) {
mgp_value_destroy(v);
}
}
};
using MgpEdgePtr = std::unique_ptr<mgp_edge, MgpEdgeDeleter>;
using MgpEdgesIteratorPtr = std::unique_ptr<mgp_edges_iterator, MgpEdgesIteratorDeleter>;
using MgpVertexPtr = std::unique_ptr<mgp_vertex, MgpVertexDeleter>;
using MgpVerticesIteratorPtr = std::unique_ptr<mgp_vertices_iterator, MgpVerticesIteratorDeleter>;
using MgpValuePtr = std::unique_ptr<mgp_value, MgpValueDeleter>;
template <typename TMaybeIterable, typename TIterableAccessor>
size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable, TIterableAccessor func) {
if (maybe_iterable.HasError()) {
ADD_FAILURE() << static_cast<std::underlying_type_t<typename TMaybeIterable::ErrorType>>(maybe_iterable.GetError());
return 0;
}
auto iterable = func(maybe_iterable.GetValue());
return std::distance(iterable.begin(), iterable.end());
}
;
void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) {
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, from->impl),
[](const auto &edge_result) { return edge_result.edges; }),
0);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, from->impl),
[](const auto &edge_result) { return edge_result.edges; }),
number_of_edges_between);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); }, to->impl),
[](const auto &edge_result) { return edge_result.edges; }),
number_of_edges_between);
EXPECT_EQ(
CountMaybeIterables(std::visit([](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); }, to->impl),
[](const auto &edge_result) { return edge_result.edges; }),
0);
}
} // namespace
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(),
memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL};
}
std::array<memgraph::storage::Gid, 2> CreateEdge() {
std::array<memgraph::storage::Gid, 2> vertex_ids{};
auto accessor = CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
for (auto i = 0; i < 2; ++i) {
vertex_ids[i] = accessor.InsertVertex().Gid();
}
auto from = accessor.FindVertex(vertex_ids[0], memgraph::storage::View::NEW);
auto to = accessor.FindVertex(vertex_ids[1], memgraph::storage::View::NEW);
EXPECT_TRUE(accessor.InsertEdge(&from.value(), &to.value(), accessor.NameToEdgeType("EDGE")).HasValue());
EXPECT_FALSE(accessor.Commit().HasError());
return vertex_ids;
}
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()}, &this->memory)};
ASSERT_NE(from, nullptr);
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, &this->memory));
ASSERT_NE(edge, nullptr);
}
memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) {
accessors_.push_back(storage->Access(ReplicationRole::MAIN, isolationLevel));
db_accessors_.emplace_back(accessors_.back().get());
return db_accessors_.back();
}
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<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>();
};
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 = this->CreateGraph(memgraph::storage::View::NEW);
EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_graph_is_mutable, &mutable_graph), 0);
}
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(ReplicationRole::MAIN, 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);
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));
}
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 = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
const auto vertex = accessor.InsertVertex();
vertex_id = vertex.Gid();
ASSERT_FALSE(accessor.Commit().HasError());
}
mgp_graph graph = this->CreateGraph();
auto read_uncommited_accessor =
this->storage->Access(ReplicationRole::MAIN, 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);
}
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(ReplicationRole::MAIN, 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()}, &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_SUCCESS(mgp_graph_detach_delete_vertex(&graph, vertex.get()));
EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
}
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 = 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 =
this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
mgp_graph immutable_graph = this->CreateGraph(memgraph::storage::View::OLD);
mgp_vertex *raw_vertex{nullptr};
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);
MgpVertexPtr vertex_to_delete{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &immutable_graph,
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);
}
TYPED_TEST(MgpGraphTest, VerticesIterator) {
{
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 = this->CreateGraph(view);
MgpVerticesIteratorPtr vertices_iter{
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) {
EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertices_iterator_underlying_graph_is_mutable, vertices_iter.get()), 0);
} else {
EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertices_iterator_underlying_graph_is_mutable, vertices_iter.get()), 0);
}
};
{
SCOPED_TRACE("View::OLD");
check_vertices_iterator(memgraph::storage::View::OLD);
}
{
SCOPED_TRACE("View::NEW");
check_vertices_iterator(memgraph::storage::View::NEW);
}
}
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);
}
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 = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
auto vertex = accessor.InsertVertex();
vertex_id = vertex.Gid();
const auto result =
vertex.SetProperty(accessor.NameToProperty(property_to_update), memgraph::storage::PropertyValue(42));
ASSERT_TRUE(result.HasValue());
ASSERT_FALSE(accessor.Commit().HasError());
}
auto read_uncommited_accessor =
this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1);
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);
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, &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()));
const auto maybe_prop = vertex_acc->GetProperty(property_id_to_update, memgraph::storage::View::NEW);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_update_to});
}
{
SCOPED_TRACE("Remove the property");
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()));
const auto maybe_prop = vertex_acc->GetProperty(property_id_to_update, memgraph::storage::View::NEW);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{});
}
{
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, &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);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_set});
}
}
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 = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
const auto vertex = accessor.InsertVertex();
vertex_id = vertex.Gid();
ASSERT_FALSE(accessor.Commit().HasError());
}
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 =
this->storage->Access(ReplicationRole::MAIN, 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)}));
};
ASSERT_NO_FATAL_FAILURE(check_label());
EXPECT_SUCCESS(mgp_vertex_add_label(vertex.get(), mgp_label{label.data()}));
ASSERT_NO_FATAL_FAILURE(check_label());
}
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 = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
auto vertex = accessor.InsertVertex();
const auto result = vertex.AddLabel(accessor.NameToLabel(label));
ASSERT_TRUE(result.HasValue());
ASSERT_TRUE(*result);
vertex_id = vertex.Gid();
ASSERT_FALSE(accessor.Commit().HasError());
}
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 =
this->storage->Access(ReplicationRole::MAIN, 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);
};
ASSERT_NO_FATAL_FAILURE(check_label());
EXPECT_SUCCESS(mgp_vertex_remove_label(vertex.get(), mgp_label{label.data()}));
ASSERT_NO_FATAL_FAILURE(check_label());
}
TYPED_TEST(MgpGraphTest, ModifyImmutableVertex) {
static constexpr std::string_view label_to_remove{"label_to_remove"};
memgraph::storage::Gid vertex_id{};
{
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 = 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, &this->memory)};
EXPECT_EQ(mgp_vertex_set_property(vertex.get(), "property", value.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT);
}
TYPED_TEST(MgpGraphTest, CreateDeleteEdge) {
std::array<memgraph::storage::Gid, 2> vertex_ids{};
{
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 = this->CreateGraph();
MgpVertexPtr from{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph,
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()}, &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"}, &this->memory)};
CheckEdgeCountBetween(from, to, 1);
ASSERT_NE(edge, nullptr);
EXPECT_SUCCESS(mgp_graph_delete_edge(&graph, edge.get()));
CheckEdgeCountBetween(from, to, 0);
}
TYPED_TEST(MgpGraphTest, CreateDeleteEdgeWithImmutableGraph) {
memgraph::storage::Gid from_id;
memgraph::storage::Gid to_id;
{
auto accessor = this->CreateDbAccessor(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION);
auto from = accessor.InsertVertex();
auto to = accessor.InsertVertex();
from_id = from.Gid();
to_id = to.Gid();
ASSERT_TRUE(accessor.InsertEdge(&from, &to, accessor.NameToEdgeType("EDGE_TYPE_TO_REMOVE")).HasValue());
ASSERT_FALSE(accessor.Commit().HasError());
}
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"}, &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(), &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, &this->memory)};
EXPECT_EQ(mgp_graph_delete_edge(&graph, edge_copy_of_immutable.get()), mgp_error::MGP_ERROR_IMMUTABLE_OBJECT);
CheckEdgeCountBetween(from, to, 1);
}
TYPED_TEST(MgpGraphTest, EdgeIsMutable) {
const auto vertex_ids = this->CreateEdge();
auto graph = this->CreateGraph();
MgpEdgePtr 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);
}
TYPED_TEST(MgpGraphTest, MutableFromTo) {
memgraph::storage::Gid from_vertex_id{};
{
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 = this->CreateGraph(view);
MgpEdgePtr 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) {
EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge.get()) != 0, is_mutable);
EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, from) != 0, is_mutable);
EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_underlying_graph_is_mutable, to) != 0, is_mutable);
};
if (view == memgraph::storage::View::NEW) {
check_is_mutable(true);
} else {
check_is_mutable(false);
}
};
{
SCOPED_TRACE("View::OLD");
check_edges_iterator(memgraph::storage::View::OLD);
}
{
SCOPED_TRACE("View::NEW");
check_edges_iterator(memgraph::storage::View::NEW);
}
}
TYPED_TEST(MgpGraphTest, EdgesIterator) {
memgraph::storage::Gid from_vertex_id{};
{
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 = 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()}, &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);
EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_edge_underlying_graph_is_mutable, edge) != 0, is_mutable);
};
if (view == memgraph::storage::View::NEW) {
check_is_mutable(true);
} else {
check_is_mutable(false);
}
};
{
SCOPED_TRACE("View::OLD");
check_edges_iterator(memgraph::storage::View::OLD);
}
{
SCOPED_TRACE("View::NEW");
check_edges_iterator(memgraph::storage::View::NEW);
}
}
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 std::move(from->OutEdges(memgraph::storage::View::NEW).GetValue().edges.front());
};
{
const auto vertex_ids = this->CreateEdge();
from_vertex_id = vertex_ids[0];
auto accessor = this->storage->Access(ReplicationRole::MAIN, 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));
ASSERT_TRUE(result.HasValue());
ASSERT_FALSE(accessor->Commit().HasError());
}
auto read_uncommited_accessor =
this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW);
MgpEdgePtr edge;
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);
{
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, &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()));
const auto maybe_prop = edge_acc.GetProperty(property_id_to_update, memgraph::storage::View::NEW);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_update_to});
}
{
SCOPED_TRACE("Remove the property");
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()));
const auto maybe_prop = edge_acc.GetProperty(property_id_to_update, memgraph::storage::View::NEW);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{});
}
{
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, &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);
ASSERT_TRUE(maybe_prop.HasValue());
EXPECT_EQ(*maybe_prop, memgraph::storage::PropertyValue{numerical_value_to_set});
}
}
TYPED_TEST(MgpGraphTest, EdgeSetPropertyWithImmutableGraph) {
memgraph::storage::Gid from_vertex_id{};
{
const auto vertex_ids = this->CreateEdge();
from_vertex_id = vertex_ids[0];
}
auto graph = this->CreateGraph(memgraph::storage::View::OLD);
MgpEdgePtr edge;
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);
}