Decoupling Interpreter from Storage (#1186)

Unique/global InterpreterContext that is Storage agnostic (has a reference to the DbmsHandler instead)

* InterpreterContext is no longer the owner of Storage
* New Database structure that handles Storage, Triggers, Streams
* Renamed SessinContextHandler to DbmsHandler and simplified the multi-tenant logic
* Added Gatekeeper and updated handlers to use it

---------

Co-authored-by: Gareth Lloyd <gareth.lloyd@memgraph.io>
This commit is contained in:
andrejtonev
2023-09-20 13:13:54 +02:00
committed by GitHub
parent 404cdf05d3
commit bce48361ca
67 changed files with 2947 additions and 3389 deletions

View File

@@ -10,28 +10,35 @@
// licenses/APL.txt.
#include <benchmark/benchmark.h>
#include <memory>
#include "communication/result_stream_faker.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
#include "utils/logging.hpp"
class ExpansionBenchFixture : public benchmark::Fixture {
protected:
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"};
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk{memgraph::storage::Config{
.durability.storage_directory = data_directory, .disk.main_storage_directory = data_directory / "disk"}};
void SetUp(const benchmark::State &state) override {
interpreter_context.emplace(memgraph::storage::Config{}, memgraph::query::InterpreterConfig{}, data_directory);
auto *db = interpreter_context->db.get();
auto db_acc_opt = db_gk->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
interpreter_context.emplace(memgraph::query::InterpreterConfig{}, nullptr);
auto label = db->NameToLabel("Starting");
auto label = db_acc->storage()->NameToLabel("Starting");
{
auto dba = db->Access();
auto dba = db_acc->Access();
for (int i = 0; i < state.range(0); i++) dba->CreateVertex();
// the fixed part is one vertex expanding to 1000 others
@@ -45,14 +52,15 @@ class ExpansionBenchFixture : public benchmark::Fixture {
MG_ASSERT(!dba->Commit().HasError());
}
MG_ASSERT(!db->CreateIndex(label).HasError());
MG_ASSERT(!db_acc->storage()->CreateIndex(label).HasError());
interpreter.emplace(&*interpreter_context);
interpreter.emplace(&*interpreter_context, std::move(db_acc));
}
void TearDown(const benchmark::State &) override {
interpreter = std::nullopt;
interpreter_context = std::nullopt;
db_gk.reset();
std::filesystem::remove_all(data_directory);
}
};
@@ -61,7 +69,7 @@ BENCHMARK_DEFINE_F(ExpansionBenchFixture, Match)(benchmark::State &state) {
auto query = "MATCH (s:Starting) return s";
while (state.KeepRunning()) {
ResultStreamFaker results(interpreter_context->db.get());
ResultStreamFaker results(interpreter->db_acc_->get()->storage());
interpreter->Prepare(query, {}, nullptr);
interpreter->PullAll(&results);
}
@@ -76,7 +84,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(interpreter_context->db.get());
ResultStreamFaker results(interpreter->db_acc_->get()->storage());
interpreter->Prepare(query, {}, nullptr);
interpreter->PullAll(&results);
}

View File

@@ -13,6 +13,7 @@
#include "license/license.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
@@ -31,11 +32,15 @@ int main(int argc, char *argv[]) {
memgraph::utils::OnScopeExit([&data_directory] { std::filesystem::remove_all(data_directory); });
memgraph::license::global_license_checker.EnableTesting();
memgraph::query::InterpreterContext interpreter_context{memgraph::storage::Config{},
memgraph::query::InterpreterConfig{}, data_directory};
memgraph::query::Interpreter interpreter{&interpreter_context};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(memgraph::storage::Config{
.durability.storage_directory = data_directory, .disk.main_storage_directory = data_directory / "disk"});
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
memgraph::query::Interpreter interpreter{&interpreter_context, db_acc};
ResultStreamFaker stream(interpreter_context.db.get());
ResultStreamFaker stream(db_acc->storage());
auto [header, _1, qid, _2] = interpreter.Prepare(argv[1], {}, nullptr);
stream.Header(header);
auto summary = interpreter.PullAll(&stream);

View File

@@ -257,9 +257,6 @@ target_link_libraries(${test_prefix}utils_signals mg-utils)
add_unit_test(utils_string.cpp)
target_link_libraries(${test_prefix}utils_string mg-utils)
add_unit_test(utils_sync_ptr.cpp)
target_link_libraries(${test_prefix}utils_sync_ptr)
add_unit_test(utils_synchronized.cpp)
target_link_libraries(${test_prefix}utils_synchronized mg-utils)
@@ -404,17 +401,9 @@ target_link_libraries(${test_prefix}monitoring mg-communication Boost::headers)
# Test multi-database
if(MG_ENTERPRISE)
# add_unit_test(dbms_storage.cpp)
# target_link_libraries(${test_prefix}dbms_storage mg-storage-v2 mg-query mg-glue)
add_unit_test(dbms_database.cpp)
target_link_libraries(${test_prefix}dbms_database mg-storage-v2 mg-query mg-glue mg-dbms)
add_unit_test(dbms_interp.cpp)
target_link_libraries(${test_prefix}dbms_interp mg-query)
endif()
# add_unit_test(dbms_auth.cpp)
# target_link_libraries(${test_prefix}dbms_auth mg-glue)
if(MG_ENTERPRISE)
add_unit_test_with_custom_main(dbms_sc_handler.cpp)
target_link_libraries(${test_prefix}dbms_sc_handler mg-query mg-audit mg-glue)
add_unit_test_with_custom_main(dbms_handler.cpp)
target_link_libraries(${test_prefix}dbms_handler mg-query mg-auth mg-glue mg-dbms)
endif()

View File

@@ -119,13 +119,6 @@ class TestSession final : public Session<TestInputStream, TestOutputStream> {
void Configure(const std::map<std::string, memgraph::communication::bolt::Value> &) override {}
std::string GetDatabaseName() const override { return ""; }
#ifdef MG_ENTERPRISE
memgraph::dbms::SetForResult OnChange(const std::string &db_name) override {
return memgraph::dbms::SetForResult::SUCCESS;
}
bool OnDelete(const std::string &) override { return true; }
#endif
void TestHook_ShouldAbort() { should_abort_ = true; }
private:

View File

@@ -0,0 +1,224 @@
// 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 <filesystem>
#include "dbms/database_handler.hpp"
#include "dbms/global.hpp"
#include "license/license.hpp"
#include "query_plan_common.hpp"
#include "storage/v2/view.hpp"
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_database"};
memgraph::storage::Config default_conf(std::string name = "") {
return {.durability = {.storage_directory = storage_directory / name,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / name / "disk"}};
}
class DBMS_Database : public ::testing::Test {
protected:
void SetUp() override { Clear(); }
void TearDown() override { Clear(); }
private:
void Clear() {
if (std::filesystem::exists(storage_directory)) {
std::filesystem::remove_all(storage_directory);
}
}
};
#ifdef MG_ENTERPRISE
TEST_F(DBMS_Database, New) {
memgraph::dbms::DatabaseHandler db_handler;
{ ASSERT_FALSE(db_handler.GetConfig("db1")); }
{ // With custom config
memgraph::storage::Config db_config{
.durability = {.storage_directory = storage_directory / "db2",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto db2 = db_handler.New("db2", db_config);
ASSERT_TRUE(db2.HasValue() && db2.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db2"));
}
{
// With default config
auto db3 = db_handler.New("db3", default_conf("db3"));
ASSERT_TRUE(db3.HasValue() && db3.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db3"));
auto db4 = db_handler.New("db4", default_conf("four"));
ASSERT_TRUE(db4.HasValue() && db4.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "four"));
auto db5 = db_handler.New("db5", default_conf("db3"));
ASSERT_TRUE(db5.HasError() && db5.GetError() == memgraph::dbms::NewError::EXISTS);
}
auto all = db_handler.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 3);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
ASSERT_EQ(all[2], "db4");
}
TEST_F(DBMS_Database, Get) {
memgraph::dbms::DatabaseHandler db_handler;
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db3 = db_handler.New("db3", default_conf("db3"));
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
auto get_db1 = db_handler.Get("db1");
auto get_db2 = db_handler.Get("db2");
auto get_db3 = db_handler.Get("db3");
ASSERT_TRUE(get_db1 && *get_db1 == db1.GetValue());
ASSERT_TRUE(get_db2 && *get_db2 == db2.GetValue());
ASSERT_TRUE(get_db3 && *get_db3 == db3.GetValue());
ASSERT_FALSE(db_handler.Get("db123"));
ASSERT_FALSE(db_handler.Get("db2 "));
ASSERT_FALSE(db_handler.Get(" db3"));
}
TEST_F(DBMS_Database, Delete) {
memgraph::dbms::DatabaseHandler db_handler;
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db3 = db_handler.New("db3", default_conf("db3"));
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
{
// Release accessor to storage
db1.GetValue().reset();
// Delete from handler
ASSERT_TRUE(db_handler.Delete("db1"));
ASSERT_FALSE(db_handler.Get("db1"));
auto all = db_handler.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 2);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
}
{
ASSERT_THROW(db_handler.Delete("db0"), memgraph::utils::BasicException);
ASSERT_THROW(db_handler.Delete("db1"), memgraph::utils::BasicException);
auto all = db_handler.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 2);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
}
}
TEST_F(DBMS_Database, DeleteAndRecover) {
memgraph::license::global_license_checker.EnableTesting();
memgraph::dbms::DatabaseHandler db_handler;
{
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
memgraph::storage::Config conf_w_snap{
.durability = {.storage_directory = storage_directory / "db3",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
.snapshot_on_exit = true},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = db_handler.New("db3", conf_w_snap);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
// Add data to graphs
{
auto storage_dba = db1.GetValue()->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l11")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l12")).HasValue());
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto storage_dba = db3.GetValue()->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
memgraph::query::VertexAccessor v3{dba.InsertVertex()};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l31")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l32")).HasValue());
ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l33")).HasValue());
ASSERT_FALSE(dba.Commit().HasError());
}
}
// Delete from handler
ASSERT_TRUE(db_handler.Delete("db1"));
ASSERT_TRUE(db_handler.Delete("db2"));
ASSERT_TRUE(db_handler.Delete("db3"));
{
// Recover graphs (only db3)
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
memgraph::storage::Config conf_w_rec{
.durability = {.storage_directory = storage_directory / "db3",
.recover_on_startup = true,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = db_handler.New("db3", conf_w_rec);
// Check content
{
// Empty
auto storage_dba = db1.GetValue()->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 0);
}
{
// Empty
auto storage_dba = db2.GetValue()->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 0);
}
{
// Full
auto storage_dba = db3.GetValue()->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 3);
}
}
}
#endif

193
tests/unit/dbms_handler.cpp Normal file
View File

@@ -0,0 +1,193 @@
// 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 "query/auth_query_handler.hpp"
#ifdef MG_ENTERPRISE
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include <system_error>
#include "dbms/constants.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/global.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
// Global
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_handler"};
static memgraph::storage::Config storage_conf;
std::unique_ptr<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>> auth;
// Let this be global so we can test it different states throughout
class TestEnvironment : public ::testing::Environment {
public:
static memgraph::dbms::DbmsHandler *get() { return ptr_.get(); }
void SetUp() override {
// Setup config
memgraph::storage::UpdatePaths(storage_conf, storage_directory);
storage_conf.durability.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL;
// Clean storage directory (running multiple parallel test, run only if the first process)
if (std::filesystem::exists(storage_directory)) {
memgraph::utils::OutputFile lock_file_handle_;
lock_file_handle_.Open(storage_directory / ".lock", memgraph::utils::OutputFile::Mode::OVERWRITE_EXISTING);
if (lock_file_handle_.AcquireLock()) {
std::filesystem::remove_all(storage_directory);
}
}
auth =
std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>(
storage_directory / "auth");
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false, true);
}
void TearDown() override {
ptr_.reset();
auth.reset();
}
static std::unique_ptr<memgraph::dbms::DbmsHandler> ptr_;
};
std::unique_ptr<memgraph::dbms::DbmsHandler> TestEnvironment::ptr_ = nullptr;
class DBMS_Handler : public testing::Test {};
using DBMS_HandlerDeath = DBMS_Handler;
TEST(DBMS_Handler, Init) {
// Check that the default db has been created successfully
std::vector<std::string> dirs = {"snapshots", "streams", "triggers", "wal"};
for (const auto &dir : dirs)
ASSERT_TRUE(std::filesystem::exists(storage_directory / dir)) << (storage_directory / dir);
const auto db_path = storage_directory / "databases" / memgraph::dbms::kDefaultDB;
ASSERT_TRUE(std::filesystem::exists(db_path));
for (const auto &dir : dirs) {
std::error_code ec;
const auto test_link = std::filesystem::read_symlink(db_path / dir, ec);
ASSERT_TRUE(!ec) << ec.message();
ASSERT_EQ(test_link, "../../" + dir);
}
}
TEST(DBMS_Handler, New) {
auto &dbms = *TestEnvironment::get();
{
const auto all = dbms.All();
ASSERT_EQ(all.size(), 1);
ASSERT_EQ(all[0], memgraph::dbms::kDefaultDB);
}
{
auto db1 = dbms.New("db1");
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db1.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db1"));
ASSERT_TRUE(db1.GetValue()->storage() != nullptr);
ASSERT_TRUE(db1.GetValue()->streams() != nullptr);
ASSERT_TRUE(db1.GetValue()->trigger_store() != nullptr);
ASSERT_TRUE(db1.GetValue()->thread_pool() != nullptr);
const auto all = dbms.All();
ASSERT_EQ(all.size(), 2);
ASSERT_TRUE(std::find(all.begin(), all.end(), memgraph::dbms::kDefaultDB) != all.end());
ASSERT_TRUE(std::find(all.begin(), all.end(), "db1") != all.end());
}
{
// Fail if name exists
auto db2 = dbms.New("db1");
ASSERT_TRUE(db2.HasError() && db2.GetError() == memgraph::dbms::NewError::EXISTS);
}
{
auto db3 = dbms.New("db3");
ASSERT_TRUE(db3.HasValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db3"));
ASSERT_TRUE(db3.GetValue()->storage() != nullptr);
ASSERT_TRUE(db3.GetValue()->streams() != nullptr);
ASSERT_TRUE(db3.GetValue()->trigger_store() != nullptr);
ASSERT_TRUE(db3.GetValue()->thread_pool() != nullptr);
const auto all = dbms.All();
ASSERT_EQ(all.size(), 3);
ASSERT_TRUE(std::find(all.begin(), all.end(), "db3") != all.end());
}
}
TEST(DBMS_Handler, Get) {
auto &dbms = *TestEnvironment::get();
auto default_db = dbms.Get(memgraph::dbms::kDefaultDB);
ASSERT_TRUE(default_db);
ASSERT_TRUE(default_db->storage() != nullptr);
ASSERT_TRUE(default_db->streams() != nullptr);
ASSERT_TRUE(default_db->trigger_store() != nullptr);
ASSERT_TRUE(default_db->thread_pool() != nullptr);
ASSERT_ANY_THROW(dbms.Get("non-existent"));
auto db1 = dbms.Get("db1");
ASSERT_TRUE(db1);
ASSERT_TRUE(db1->storage() != nullptr);
ASSERT_TRUE(db1->streams() != nullptr);
ASSERT_TRUE(db1->trigger_store() != nullptr);
ASSERT_TRUE(db1->thread_pool() != nullptr);
auto db3 = dbms.Get("db3");
ASSERT_TRUE(db3);
ASSERT_TRUE(db3->storage() != nullptr);
ASSERT_TRUE(db3->streams() != nullptr);
ASSERT_TRUE(db3->trigger_store() != nullptr);
ASSERT_TRUE(db3->thread_pool() != nullptr);
}
TEST(DBMS_Handler, Delete) {
auto &dbms = *TestEnvironment::get();
auto db1_acc = dbms.Get("db1"); // Holds access to database
{
auto del = dbms.Delete(memgraph::dbms::kDefaultDB);
ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::DEFAULT_DB);
}
{
auto del = dbms.Delete("non-existent");
ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
}
{
// db1_acc is using db1
auto del = dbms.Delete("db1");
ASSERT_TRUE(del.HasError());
ASSERT_TRUE(del.GetError() == memgraph::dbms::DeleteError::USING);
}
{
// Reset db1_acc (releases access) so delete will succeed
db1_acc.reset();
ASSERT_FALSE(db1_acc);
auto del = dbms.Delete("db1");
ASSERT_FALSE(del.HasError()) << (int)del.GetError();
auto del2 = dbms.Delete("db1");
ASSERT_TRUE(del2.HasError() && del2.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
}
{
auto del = dbms.Delete("db3");
ASSERT_FALSE(del.HasError());
ASSERT_FALSE(std::filesystem::exists(storage_directory / "databases" / "db3"));
}
}
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
// gtest takes ownership of the TestEnvironment ptr - we don't delete it.
::testing::AddGlobalTestEnvironment(new TestEnvironment);
return RUN_ALL_TESTS();
}
#endif

View File

@@ -1,431 +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.
#ifdef MG_ENTERPRISE
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include "dbms/global.hpp"
#include "dbms/interp_handler.hpp"
#include "query/auth_checker.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/interpreter.hpp"
class TestAuthHandler : public memgraph::query::AuthQueryHandler {
public:
TestAuthHandler() = default;
bool CreateUser(const std::string & /*username*/, const std::optional<std::string> & /*password*/) override {
return true;
}
bool DropUser(const std::string & /*username*/) override { return true; }
void SetPassword(const std::string & /*username*/, const std::optional<std::string> & /*password*/) override {}
bool RevokeDatabaseFromUser(const std::string & /*db*/, const std::string & /*username*/) override { return true; }
bool GrantDatabaseToUser(const std::string & /*db*/, const std::string & /*username*/) override { return true; }
bool SetMainDatabase(const std::string & /*db*/, const std::string & /*username*/) override { return true; }
std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string & /*user*/) override {
return {};
}
bool CreateRole(const std::string & /*rolename*/) override { return true; }
bool DropRole(const std::string & /*rolename*/) override { return true; }
std::vector<memgraph::query::TypedValue> GetUsernames() override { return {}; }
std::vector<memgraph::query::TypedValue> GetRolenames() override { return {}; }
std::optional<std::string> GetRolenameForUser(const std::string & /*username*/) override { return {}; }
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string & /*rolename*/) override { return {}; }
void SetRole(const std::string &username, const std::string & /*rolename*/) override {}
void ClearRole(const std::string &username) override {}
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string & /*user_or_role*/) override {
return {};
}
void GrantPrivilege(
const std::string & /*user_or_role*/, const std::vector<memgraph::query::AuthQuery::Privilege> & /*privileges*/,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
& /*label_privileges*/,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
& /*edge_type_privileges*/) override {}
void DenyPrivilege(const std::string & /*user_or_role*/,
const std::vector<memgraph::query::AuthQuery::Privilege> & /*privileges*/) override {}
void RevokePrivilege(
const std::string & /*user_or_role*/, const std::vector<memgraph::query::AuthQuery::Privilege> & /*privileges*/,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
& /*label_privileges*/,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
& /*edge_type_privileges*/) override {}
};
class TestAuthChecker : public memgraph::query::AuthChecker {
public:
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
const std::vector<memgraph::query::AuthQuery::Privilege> & /*privileges*/,
const std::string & /*db*/) const override {
return true;
}
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string & /*username*/, const memgraph::query::DbAccessor * /*db_accessor*/) const override {
return {};
}
void ClearCache() const override {}
};
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_interp"};
memgraph::query::InterpreterConfig default_conf{};
memgraph::storage::Config default_storage_conf(std::string name = "") {
return {.durability = {.storage_directory = storage_directory / name,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / name / "disk"}};
}
class TestHandler {
} test_handler;
class DBMS_Interp : public ::testing::Test {
protected:
void SetUp() override { Clear(); }
void TearDown() override { Clear(); }
private:
void Clear() {
if (std::filesystem::exists(storage_directory)) {
std::filesystem::remove_all(storage_directory);
}
}
};
TEST_F(DBMS_Interp, New) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
memgraph::storage::Config db_conf{
.durability = {.storage_directory = storage_directory,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
TestAuthHandler ah;
TestAuthChecker ac;
{
// Clean initialization
auto ic1 = ih.New("ic1", test_handler, db_conf, default_conf, ah, ac);
ASSERT_TRUE(ic1.HasValue() && ic1.GetValue() != nullptr);
ASSERT_TRUE(std::filesystem::exists(storage_directory / "triggers"));
ASSERT_TRUE(std::filesystem::exists(storage_directory / "streams"));
ASSERT_NE(ic1.GetValue()->db, nullptr);
ASSERT_EQ(&ic1.GetValue()->sc_handler_, &test_handler);
ASSERT_EQ(ih.GetConfig("ic1")->storage_config.durability.storage_directory, storage_directory);
}
{
memgraph::storage::Config db_conf2{
.durability = {.storage_directory = storage_directory,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
// Try to override data directory
auto ic2 = ih.New("ic2", test_handler, db_conf2, default_conf, ah, ac);
ASSERT_TRUE(ic2.HasError() && ic2.GetError() == memgraph::dbms::NewError::EXISTS);
}
{
memgraph::storage::Config db_conf3{
.durability = {.storage_directory = storage_directory / "ic3",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
// Try to override the name "ic1"
auto ic3 = ih.New("ic1", test_handler, db_conf3, default_conf, ah, ac);
ASSERT_TRUE(ic3.HasError() && ic3.GetError() == memgraph::dbms::NewError::EXISTS);
}
{
// Another clean initialization
memgraph::storage::Config db_conf4{
.durability = {.storage_directory = storage_directory / "ic4",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto ic4 = ih.New("ic4", test_handler, db_conf4, default_conf, ah, ac);
ASSERT_TRUE(ic4.HasValue() && ic4.GetValue() != nullptr);
ASSERT_TRUE(std::filesystem::exists(storage_directory / "ic4" / "triggers"));
ASSERT_TRUE(std::filesystem::exists(storage_directory / "ic4" / "streams"));
ASSERT_EQ(&ic4.GetValue()->sc_handler_, &test_handler);
ASSERT_EQ(ih.GetConfig("ic4")->storage_config.durability.storage_directory, storage_directory / "ic4");
}
}
TEST_F(DBMS_Interp, Get) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
memgraph::storage::Config db_conf{
.durability = {.storage_directory = storage_directory / "ic1",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto ic1 = ih.New("ic1", test_handler, db_conf, default_conf, ah, ac);
ASSERT_TRUE(ic1.HasValue() && ic1.GetValue() != nullptr);
auto ic1_get = ih.Get("ic1");
ASSERT_TRUE(ic1_get && *ic1_get == ic1.GetValue());
memgraph::storage::Config db_conf2{
.durability = {.storage_directory = storage_directory / "ic2",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto ic2 = ih.New("ic2", test_handler, db_conf2, default_conf, ah, ac);
ASSERT_TRUE(ic2.HasValue() && ic2.GetValue() != nullptr);
auto ic2_get = ih.Get("ic2");
ASSERT_TRUE(ic2_get && *ic2_get == ic2.GetValue());
ASSERT_FALSE(ih.Get("aa"));
ASSERT_FALSE(ih.Get("ic1 "));
ASSERT_FALSE(ih.Get("ic21"));
ASSERT_FALSE(ih.Get(" ic2"));
}
TEST_F(DBMS_Interp, Delete) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
memgraph::storage::Config db_conf{
.durability = {.storage_directory = storage_directory / "ic1",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
{
auto ic1 = ih.New("ic1", test_handler, db_conf, default_conf, ah, ac);
ASSERT_TRUE(ic1.HasValue() && ic1.GetValue() != nullptr);
}
memgraph::storage::Config db_conf2{
.durability = {.storage_directory = storage_directory / "ic2",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
{
auto ic2 = ih.New("ic2", test_handler, db_conf2, default_conf, ah, ac);
ASSERT_TRUE(ic2.HasValue() && ic2.GetValue() != nullptr);
}
ASSERT_TRUE(ih.Delete("ic1"));
ASSERT_FALSE(ih.Get("ic1"));
ASSERT_FALSE(ih.Delete("ic1"));
ASSERT_FALSE(ih.Delete("ic3"));
}
/**
*
*
*
*
*
* Test storage (previous StorageHandler, now handled via InterpretContext)
*
*
*
*
*
*/
TEST_F(DBMS_Interp, StorageNew) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
{ ASSERT_FALSE(ih.GetConfig("db1")); }
{
// With custom config
memgraph::storage::Config db_config{
.durability = {.storage_directory = storage_directory / "db2",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto db2 = ih.New("db2", test_handler, db_config, default_conf, ah, ac);
ASSERT_TRUE(db2.HasValue() && db2.GetValue() != nullptr);
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db2"));
}
{
// With default config
auto db3 = ih.New("db3", test_handler, default_storage_conf("db3"), default_conf, ah, ac);
ASSERT_TRUE(db3.HasValue() && db3.GetValue() != nullptr);
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db3"));
auto db4 = ih.New("db4", test_handler, default_storage_conf("four"), default_conf, ah, ac);
ASSERT_TRUE(db4.HasValue() && db4.GetValue() != nullptr);
ASSERT_TRUE(std::filesystem::exists(storage_directory / "four"));
auto db5 = ih.New("db5", test_handler, default_storage_conf("db3"), default_conf, ah, ac);
ASSERT_TRUE(db5.HasError() && db5.GetError() == memgraph::dbms::NewError::EXISTS);
}
auto all = ih.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 3);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
ASSERT_EQ(all[2], "db4");
}
TEST_F(DBMS_Interp, StorageGet) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
auto db1 = ih.New("db1", test_handler, default_storage_conf("db1"), default_conf, ah, ac);
auto db2 = ih.New("db2", test_handler, default_storage_conf("db2"), default_conf, ah, ac);
auto db3 = ih.New("db3", test_handler, default_storage_conf("db3"), default_conf, ah, ac);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
auto get_db1 = ih.Get("db1");
auto get_db2 = ih.Get("db2");
auto get_db3 = ih.Get("db3");
ASSERT_TRUE(get_db1 && *get_db1 == db1.GetValue());
ASSERT_TRUE(get_db2 && *get_db2 == db2.GetValue());
ASSERT_TRUE(get_db3 && *get_db3 == db3.GetValue());
ASSERT_FALSE(ih.Get("db123"));
ASSERT_FALSE(ih.Get("db2 "));
ASSERT_FALSE(ih.Get(" db3"));
}
TEST_F(DBMS_Interp, StorageDelete) {
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
auto db1 = ih.New("db1", test_handler, default_storage_conf("db1"), default_conf, ah, ac);
auto db2 = ih.New("db2", test_handler, default_storage_conf("db2"), default_conf, ah, ac);
auto db3 = ih.New("db3", test_handler, default_storage_conf("db3"), default_conf, ah, ac);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
{
// Release pointer to storage
db1.GetValue().reset();
// Delete from handler
ASSERT_TRUE(ih.Delete("db1"));
ASSERT_FALSE(ih.Get("db1"));
auto all = ih.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 2);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
}
{
ASSERT_FALSE(ih.Delete("db0"));
ASSERT_FALSE(ih.Delete("db1"));
auto all = ih.All();
std::sort(all.begin(), all.end());
ASSERT_EQ(all.size(), 2);
ASSERT_EQ(all[0], "db2");
ASSERT_EQ(all[1], "db3");
}
}
TEST_F(DBMS_Interp, StorageDeleteAndRecover) {
// memgraph::license::global_license_checker.EnableTesting();
memgraph::dbms::InterpContextHandler<TestHandler> ih;
TestAuthHandler ah;
TestAuthChecker ac;
{
auto db1 = ih.New("db1", test_handler, default_storage_conf("db1"), default_conf, ah, ac);
auto db2 = ih.New("db2", test_handler, default_storage_conf("db2"), default_conf, ah, ac);
memgraph::storage::Config conf_w_snap{
.durability = {.storage_directory = storage_directory / "db3",
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL,
.snapshot_on_exit = true},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = ih.New("db3", test_handler, conf_w_snap, default_conf, ah, ac);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
ASSERT_TRUE(db3.HasValue());
// Add data to graphs
{
auto storage_dba = db1.GetValue()->db->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l11")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l12")).HasValue());
ASSERT_FALSE(dba.Commit().HasError());
}
{
auto storage_dba = db3.GetValue()->db->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
memgraph::query::VertexAccessor v3{dba.InsertVertex()};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l31")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l32")).HasValue());
ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l33")).HasValue());
ASSERT_FALSE(dba.Commit().HasError());
}
}
// Delete from handler
ASSERT_TRUE(ih.Delete("db1"));
ASSERT_TRUE(ih.Delete("db2"));
ASSERT_TRUE(ih.Delete("db3"));
{
// Recover graphs (only db3)
auto db1 = ih.New("db1", test_handler, default_storage_conf("db1"), default_conf, ah, ac);
auto db2 = ih.New("db2", test_handler, default_storage_conf("db2"), default_conf, ah, ac);
memgraph::storage::Config conf_w_rec{
.durability = {.storage_directory = storage_directory / "db3",
.recover_on_startup = true,
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = ih.New("db3", test_handler, conf_w_rec, default_conf, ah, ac);
// Check content
{
// Empty
auto storage_dba = db1.GetValue()->db->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 0);
}
{
// Empty
auto storage_dba = db2.GetValue()->db->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 0);
}
{
// Full
auto storage_dba = db3.GetValue()->db->Access();
memgraph::query::DbAccessor dba{storage_dba.get()};
ASSERT_EQ(dba.VerticesCount(), 3);
}
}
}
#endif

View File

@@ -1,343 +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 <system_error>
#include "query/interpreter.hpp"
#ifdef MG_ENTERPRISE
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include "dbms/constants.hpp"
#include "dbms/global.hpp"
#include "dbms/session_context_handler.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "query/config.hpp"
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_sc_handler"};
static memgraph::storage::Config storage_conf;
memgraph::query::InterpreterConfig interp_conf;
// Global
memgraph::audit::Log audit_log{storage_directory / "audit", 100, 1000};
class TestInterface : public memgraph::dbms::SessionInterface {
public:
TestInterface(std::string name, auto on_change, auto on_delete) : id_(id++), db_(name) {
on_change_ = on_change;
on_delete_ = on_delete;
}
std::string UUID() const override { return std::to_string(id_); }
std::string GetDatabaseName() const override { return db_; }
memgraph::dbms::SetForResult OnChange(const std::string &name) override { return on_change_(name); }
bool OnDelete(const std::string &name) override { return on_delete_(name); }
static int id;
int id_;
std::string db_;
std::function<memgraph::dbms::SetForResult(const std::string &)> on_change_;
std::function<bool(const std::string &)> on_delete_;
};
int TestInterface::id{0};
// Let this be global so we can test it different states throughout
class TestEnvironment : public ::testing::Environment {
public:
static memgraph::dbms::SessionContextHandler *get() { return ptr_.get(); }
void SetUp() override {
// Setup config
memgraph::storage::UpdatePaths(storage_conf, storage_directory);
storage_conf.durability.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL;
// Clean storage directory (running multiple parallel test, run only if the first process)
if (std::filesystem::exists(storage_directory)) {
memgraph::utils::OutputFile lock_file_handle_;
lock_file_handle_.Open(storage_directory / ".lock", memgraph::utils::OutputFile::Mode::OVERWRITE_EXISTING);
if (lock_file_handle_.AcquireLock()) {
std::filesystem::remove_all(storage_directory);
}
}
ptr_ = std::make_unique<memgraph::dbms::SessionContextHandler>(
audit_log,
memgraph::dbms::SessionContextHandler::Config{
storage_conf, interp_conf,
[](memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::unique_ptr<memgraph::query::AuthQueryHandler> &ah,
std::unique_ptr<memgraph::query::AuthChecker> &ac) {
// Glue high level auth implementations to the query side
ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, "");
ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
}},
false, true);
}
void TearDown() override { ptr_.reset(); }
static std::unique_ptr<memgraph::dbms::SessionContextHandler> ptr_;
};
std::unique_ptr<memgraph::dbms::SessionContextHandler> TestEnvironment::ptr_ = nullptr;
class DBMS_Handler : public testing::Test {};
using DBMS_HandlerDeath = DBMS_Handler;
TEST(DBMS_Handler, Init) {
// Check that the default db has been created successfully
std::vector<std::string> dirs = {"snapshots", "streams", "triggers", "wal"};
for (const auto &dir : dirs)
ASSERT_TRUE(std::filesystem::exists(storage_directory / dir)) << (storage_directory / dir);
const auto db_path = storage_directory / "databases" / memgraph::dbms::kDefaultDB;
ASSERT_TRUE(std::filesystem::exists(db_path));
for (const auto &dir : dirs) {
std::error_code ec;
const auto test_link = std::filesystem::read_symlink(db_path / dir, ec);
ASSERT_TRUE(!ec) << ec.message();
ASSERT_EQ(test_link, "../../" + dir);
}
}
TEST(DBMS_HandlerDeath, InitSameDir) {
// This will be executed in a clean process (so the singleton will NOT be initalized)
(void)(::testing::GTEST_FLAG(death_test_style) = "threadsafe");
// NOTE: Init test has ran in another process (so holds the lock)
ASSERT_DEATH(
{
memgraph::dbms::SessionContextHandler sch(
audit_log,
{storage_conf, interp_conf,
[](memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::unique_ptr<memgraph::query::AuthQueryHandler> &ah,
std::unique_ptr<memgraph::query::AuthChecker> &ac) {
// Glue high level auth implementations to the query side
ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, "");
ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
}},
false, true);
},
R"(\b.*\b)");
}
TEST(DBMS_Handler, New) {
auto &sch = *TestEnvironment::get();
{
const auto all = sch.All();
ASSERT_EQ(all.size(), 1);
ASSERT_EQ(all[0], memgraph::dbms::kDefaultDB);
}
{
auto sc1 = sch.New("sc1");
ASSERT_TRUE(sc1.HasValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "sc1"));
ASSERT_TRUE(sc1.GetValue().interpreter_context->db != nullptr);
ASSERT_TRUE(sc1.GetValue().interpreter_context != nullptr);
ASSERT_TRUE(sc1.GetValue().audit_log != nullptr);
ASSERT_TRUE(sc1.GetValue().auth != nullptr);
const auto all = sch.All();
ASSERT_EQ(all.size(), 2);
ASSERT_TRUE(std::find(all.begin(), all.end(), memgraph::dbms::kDefaultDB) != all.end());
ASSERT_TRUE(std::find(all.begin(), all.end(), "sc1") != all.end());
}
{
// Fail if name exists
auto sc2 = sch.New("sc1");
ASSERT_TRUE(sc2.HasError() && sc2.GetError() == memgraph::dbms::NewError::EXISTS);
}
{
auto sc3 = sch.New("sc3");
ASSERT_TRUE(sc3.HasValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "sc3"));
ASSERT_TRUE(sc3.GetValue().interpreter_context->db != nullptr);
ASSERT_TRUE(sc3.GetValue().interpreter_context != nullptr);
ASSERT_TRUE(sc3.GetValue().audit_log != nullptr);
ASSERT_TRUE(sc3.GetValue().auth != nullptr);
const auto all = sch.All();
ASSERT_EQ(all.size(), 3);
ASSERT_TRUE(std::find(all.begin(), all.end(), "sc3") != all.end());
}
}
TEST(DBMS_Handler, Get) {
auto &sch = *TestEnvironment::get();
auto default_sc = sch.Get(memgraph::dbms::kDefaultDB);
ASSERT_TRUE(default_sc.interpreter_context->db != nullptr);
ASSERT_TRUE(default_sc.interpreter_context != nullptr);
ASSERT_TRUE(default_sc.audit_log != nullptr);
ASSERT_TRUE(default_sc.auth != nullptr);
ASSERT_ANY_THROW(sch.Get("non-existent"));
auto sc1 = sch.Get("sc1");
ASSERT_TRUE(sc1.interpreter_context->db != nullptr);
ASSERT_TRUE(sc1.interpreter_context != nullptr);
ASSERT_TRUE(sc1.audit_log != nullptr);
ASSERT_TRUE(sc1.auth != nullptr);
auto sc3 = sch.Get("sc3");
ASSERT_TRUE(sc3.interpreter_context->db != nullptr);
ASSERT_TRUE(sc3.interpreter_context != nullptr);
ASSERT_TRUE(sc3.audit_log != nullptr);
ASSERT_TRUE(sc3.auth != nullptr);
}
TEST(DBMS_Handler, SetFor) {
auto &sch = *TestEnvironment::get();
ASSERT_TRUE(sch.New("db1").HasValue());
bool ti0_on_change_ = false;
bool ti0_on_delete_ = false;
TestInterface ti0(
"memgraph",
[&ti0, &ti0_on_change_](const std::string &name) -> memgraph::dbms::SetForResult {
ti0_on_change_ = true;
if (name != ti0.db_) {
ti0.db_ = name;
return memgraph::dbms::SetForResult::SUCCESS;
}
return memgraph::dbms::SetForResult::ALREADY_SET;
},
[&](const std::string &name) -> bool {
ti0_on_delete_ = true;
return true;
});
bool ti1_on_change_ = false;
bool ti1_on_delete_ = false;
TestInterface ti1(
"db1",
[&](const std::string &name) -> memgraph::dbms::SetForResult {
ti1_on_change_ = true;
return memgraph::dbms::SetForResult::SUCCESS;
},
[&](const std::string &name) -> bool {
ti1_on_delete_ = true;
return true;
});
ASSERT_TRUE(sch.Register(ti0));
ASSERT_FALSE(sch.Register(ti0));
{
ASSERT_EQ(sch.SetFor("0", "db1"), memgraph::dbms::SetForResult::SUCCESS);
ASSERT_TRUE(ti0_on_change_);
ti0_on_change_ = false;
ASSERT_EQ(sch.SetFor("0", "db1"), memgraph::dbms::SetForResult::ALREADY_SET);
ASSERT_TRUE(ti0_on_change_);
ti0_on_change_ = false;
ASSERT_ANY_THROW(sch.SetFor(std::to_string(TestInterface::id), "db1")); // Session does not exist
ASSERT_ANY_THROW(sch.SetFor("1", "db1")); // Session not registered
ASSERT_ANY_THROW(sch.SetFor("0", "db2")); // No db2
ASSERT_EQ(sch.SetFor("0", "memgraph"), memgraph::dbms::SetForResult::SUCCESS);
ASSERT_TRUE(ti0_on_change_);
}
ASSERT_TRUE(sch.Delete(ti0));
ASSERT_FALSE(sch.Delete(ti1));
}
TEST(DBMS_Handler, Delete) {
auto &sch = *TestEnvironment::get();
bool ti0_on_change_ = false;
bool ti0_on_delete_ = false;
TestInterface ti0(
"memgraph",
[&](const std::string &name) -> memgraph::dbms::SetForResult {
ti0_on_change_ = true;
if (name != "sc3") return memgraph::dbms::SetForResult::SUCCESS;
return memgraph::dbms::SetForResult::FAIL;
},
[&](const std::string &name) -> bool {
ti0_on_delete_ = true;
return (name != "sc3");
});
bool ti1_on_change_ = false;
bool ti1_on_delete_ = false;
TestInterface ti1(
"sc1",
[&](const std::string &name) -> memgraph::dbms::SetForResult {
ti1_on_change_ = true;
ti1.db_ = name;
return memgraph::dbms::SetForResult::SUCCESS;
},
[&](const std::string &name) -> bool {
ti1_on_delete_ = true;
return ti1.db_ != name;
});
ASSERT_TRUE(sch.Register(ti0));
ASSERT_TRUE(sch.Register(ti1));
{
auto del = sch.Delete(memgraph::dbms::kDefaultDB);
ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::DEFAULT_DB);
}
{
auto del = sch.Delete("non-existent");
ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
}
{
// ti1 is using sc1
auto del = sch.Delete("sc1");
ASSERT_TRUE(del.HasError());
ASSERT_TRUE(del.GetError() == memgraph::dbms::DeleteError::FAIL);
}
{
// Delete ti1 so delete will succeed
ASSERT_EQ(sch.SetFor(ti1.UUID(), "memgraph"), memgraph::dbms::SetForResult::SUCCESS);
auto del = sch.Delete("sc1");
ASSERT_FALSE(del.HasError()) << (int)del.GetError();
auto del2 = sch.Delete("sc1");
ASSERT_TRUE(del2.HasError() && del2.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT);
}
{
// Using based on the active interpreters
auto new_sc = sch.New("sc1");
ASSERT_TRUE(new_sc.HasValue()) << (int)new_sc.GetError();
auto sc = sch.Get("sc1");
memgraph::query::Interpreter interpreter(sc.interpreter_context.get());
sc.interpreter_context->interpreters.WithLock([&](auto &interpreters) { interpreters.insert(&interpreter); });
auto del = sch.Delete("sc1");
ASSERT_TRUE(del.HasError());
ASSERT_EQ(del.GetError(), memgraph::dbms::DeleteError::USING);
sc.interpreter_context->interpreters.WithLock([&](auto &interpreters) { interpreters.erase(&interpreter); });
}
{
// Interpreter deactivated, so we should be able to delete
auto del = sch.Delete("sc1");
ASSERT_FALSE(del.HasError()) << (int)del.GetError();
}
{
ASSERT_TRUE(sch.Delete(ti0));
auto del = sch.Delete("sc3");
ASSERT_FALSE(del.HasError());
ASSERT_FALSE(std::filesystem::exists(storage_directory / "databases" / "sc3"));
}
ASSERT_TRUE(sch.Delete(ti1));
}
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
// gtest takes ownership of the TestEnvironment ptr - we don't delete it.
::testing::AddGlobalTestEnvironment(new TestEnvironment);
return RUN_ALL_TESTS();
}
#endif

View File

@@ -26,12 +26,14 @@
#include "query/config.hpp"
#include "query/exceptions.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#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 "storage/v2/storage_mode.hpp"
#include "utils/logging.hpp"
namespace {
@@ -49,20 +51,43 @@ 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.
constexpr auto kNoHandler = nullptr;
template <typename StorageType>
class InterpreterTest : public ::testing::Test {
public:
const std::string testSuite = "interpreter";
const std::string testSuiteCsv = "interpreter_csv";
std::filesystem::path data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter";
InterpreterTest()
: data_directory(std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter"),
interpreter_context(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {},
data_directory) {
memgraph::flags::run_time::execution_timeout_sec_ = 600.0;
}
InterpreterTest() : interpreter_context({}, kNoHandler) { memgraph::flags::run_time::execution_timeout_sec_ = 600.0; }
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
return db_acc;
}() // iile
};
std::filesystem::path data_directory;
memgraph::query::InterpreterContext interpreter_context;
void TearDown() override {
@@ -72,7 +97,7 @@ class InterpreterTest : public ::testing::Test {
}
}
InterpreterFaker default_interpreter{&interpreter_context};
InterpreterFaker default_interpreter{&interpreter_context, db};
auto Prepare(const std::string &query, const std::map<std::string, memgraph::storage::PropertyValue> &params = {}) {
return default_interpreter.Prepare(query, params);
@@ -320,7 +345,7 @@ TYPED_TEST(InterpreterTest, Bfs) {
// Set up.
{
auto storage_dba = this->interpreter_context.db->Access();
auto storage_dba = this->db->Access();
memgraph::query::DbAccessor dba(storage_dba.get());
auto add_node = [&](int level, bool reachable) {
auto node = dba.InsertVertex();
@@ -633,7 +658,7 @@ TYPED_TEST(InterpreterTest, UniqueConstraintTest) {
}
TYPED_TEST(InterpreterTest, ExplainQuery) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->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);
@@ -647,16 +672,16 @@ TYPED_TEST(InterpreterTest, ExplainQuery) {
++expected_it;
}
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for EXPLAIN ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ExplainQueryMultiplePulls) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->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);
@@ -680,16 +705,16 @@ TYPED_TEST(InterpreterTest, ExplainQueryMultiplePulls) {
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(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for EXPLAIN ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ExplainQueryInMulticommandTransaction) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->plan_cache()->size(), 0U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U);
this->Interpret("BEGIN");
auto stream = this->Interpret("EXPLAIN MATCH (n) RETURN *;");
@@ -705,16 +730,16 @@ TYPED_TEST(InterpreterTest, ExplainQueryInMulticommandTransaction) {
++expected_it;
}
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for EXPLAIN ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ExplainQueryWithParams) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->plan_cache()->size(), 0U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U);
auto stream =
this->Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}});
@@ -729,16 +754,16 @@ TYPED_TEST(InterpreterTest, ExplainQueryWithParams) {
++expected_it;
}
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for EXPLAIN ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ProfileQuery) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->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"};
@@ -752,16 +777,16 @@ TYPED_TEST(InterpreterTest, ProfileQuery) {
++expected_it;
}
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for PROFILE ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ProfileQueryMultiplePulls) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->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"};
@@ -788,11 +813,11 @@ TYPED_TEST(InterpreterTest, ProfileQueryMultiplePulls) {
ASSERT_EQ(stream.GetResults()[2][0].ValueString(), *expected_it);
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for PROFILE ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
@@ -803,7 +828,7 @@ TYPED_TEST(InterpreterTest, ProfileQueryInMulticommandTransaction) {
}
TYPED_TEST(InterpreterTest, ProfileQueryWithParams) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->plan_cache()->size(), 0U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 0U);
auto stream =
this->Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}});
@@ -818,16 +843,16 @@ TYPED_TEST(InterpreterTest, ProfileQueryWithParams) {
++expected_it;
}
// We should have a plan cache for MATCH ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for PROFILE ... and for inner MATCH ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
TYPED_TEST(InterpreterTest, ProfileQueryWithLiterals) {
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 0U);
EXPECT_EQ(this->db->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"};
@@ -841,11 +866,11 @@ TYPED_TEST(InterpreterTest, ProfileQueryWithLiterals) {
++expected_it;
}
// We should have a plan cache for UNWIND ...
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
// We should have AST cache for PROFILE ... and for inner UNWIND ...
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->db->plan_cache()->size(), 1U);
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 2U);
}
@@ -1071,7 +1096,7 @@ TYPED_TEST(InterpreterTest, CacheableQueries) {
SCOPED_TRACE("Cacheable query");
this->Interpret("RETURN 1");
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 1U);
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
}
{
@@ -1080,7 +1105,7 @@ TYPED_TEST(InterpreterTest, CacheableQueries) {
// result signature could be changed
this->Interpret("CALL mg.load_all()");
EXPECT_EQ(this->interpreter_context.ast_cache.size(), 1U);
EXPECT_EQ(this->interpreter_context.plan_cache.size(), 1U);
EXPECT_EQ(this->db->plan_cache()->size(), 1U);
}
}
@@ -1098,11 +1123,25 @@ TYPED_TEST(InterpreterTest, AllowLoadCsvConfig) {
"CREATE TRIGGER trigger ON CREATE BEFORE COMMIT EXECUTE LOAD CSV FROM 'file.csv' WITH HEADER AS row RETURN "
"row"};
memgraph::query::InterpreterContext csv_interpreter_context{
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};
memgraph::storage::Config config2{};
config2.durability.storage_directory = directory_manager.Path();
config2.disk.main_storage_directory = config2.durability.storage_directory / "disk";
if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) {
config2.disk = disk_test_utils::GenerateOnDiskConfig(this->testSuiteCsv).disk;
config2.force_on_disk = true;
}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk2(config2);
auto db_acc_opt = db_gk2.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db2";
auto &db_acc = *db_acc_opt;
ASSERT_TRUE(db_acc->GetStorageMode() == (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext csv_interpreter_context{{.query = {.allow_load_csv = allow_load_csv}}, nullptr};
InterpreterFaker interpreter_faker{&csv_interpreter_context, db_acc};
for (const auto &query : queries) {
if (allow_load_csv) {
SCOPED_TRACE(fmt::format("'{}' should not throw because LOAD CSV is allowed", query));

View File

@@ -11,16 +11,17 @@
#include "communication/result_stream_faker.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
struct InterpreterFaker {
InterpreterFaker(memgraph::query::InterpreterContext *interpreter_context)
: interpreter_context(interpreter_context), interpreter(interpreter_context) {
InterpreterFaker(memgraph::query::InterpreterContext *interpreter_context, memgraph::dbms::DatabaseAccess db)
: interpreter_context(interpreter_context), interpreter(interpreter_context, db) {
interpreter_context->auth_checker = &auth_checker;
interpreter_context->interpreters.WithLock([this](auto &interpreters) { interpreters.insert(&interpreter); });
}
auto Prepare(const std::string &query, const std::map<std::string, memgraph::storage::PropertyValue> &params = {}) {
ResultStreamFaker stream(interpreter_context->db.get());
ResultStreamFaker stream(interpreter.db_acc_->get()->storage());
const auto [header, _1, qid, _2] = interpreter.Prepare(query, params, nullptr);
stream.Header(header);
return std::make_pair(std::move(stream), qid);

View File

@@ -17,10 +17,13 @@
#include <vector>
#include "communication/result_stream_faker.hpp"
#include "dbms/database.hpp"
#include "disk_test_utils.hpp"
#include "query/config.hpp"
#include "query/dump.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/stream/streams.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/edge_accessor.hpp"
@@ -205,9 +208,10 @@ DatabaseState GetState(memgraph::storage::Storage *db) {
return {vertices, edges, label_indices, label_property_indices, existence_constraints, unique_constraints};
}
auto Execute(memgraph::query::InterpreterContext *context, const std::string &query) {
memgraph::query::Interpreter interpreter(context);
ResultStreamFaker stream(context->db.get());
auto Execute(memgraph::query::InterpreterContext *context, memgraph::dbms::DatabaseAccess db,
const std::string &query) {
memgraph::query::Interpreter interpreter(context, db);
ResultStreamFaker stream(db->storage());
auto [header, _1, qid, _2] = interpreter.Prepare(query, {}, nullptr);
stream.Header(header);
@@ -275,14 +279,40 @@ 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};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext context{memgraph::query::InterpreterConfig{}, nullptr};
void TearDown() override {
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}
std::filesystem::remove_all(data_directory);
}
};
@@ -291,10 +321,10 @@ TYPED_TEST_CASE(DumpTest, StorageTypes);
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, EmptyGraph) {
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -304,16 +334,16 @@ TYPED_TEST(DumpTest, EmptyGraph) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, SingleVertex) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {}, {}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -325,16 +355,16 @@ TYPED_TEST(DumpTest, SingleVertex) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, VertexWithSingleLabel) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {"Label1"}, {}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -346,16 +376,16 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {"Label1", "Label 2"}, {}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -368,16 +398,16 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, VertexWithSingleProperty) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {}, {{"prop", memgraph::storage::PropertyValue(42)}}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -389,7 +419,7 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, MultipleVertices) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {}, {}, false);
CreateVertex(dba.get(), {}, {}, false);
CreateVertex(dba.get(), {}, {}, false);
@@ -397,10 +427,10 @@ TYPED_TEST(DumpTest, MultipleVertices) {
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -412,7 +442,7 @@ TYPED_TEST(DumpTest, MultipleVertices) {
TYPED_TEST(DumpTest, PropertyValue) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
auto null_value = memgraph::storage::PropertyValue();
auto int_value = memgraph::storage::PropertyValue(13);
auto bool_value = memgraph::storage::PropertyValue(true);
@@ -435,10 +465,10 @@ TYPED_TEST(DumpTest, PropertyValue) {
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -454,7 +484,7 @@ TYPED_TEST(DumpTest, PropertyValue) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, SingleEdge) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
auto u = CreateVertex(dba.get(), {}, {}, false);
auto v = CreateVertex(dba.get(), {}, {}, false);
CreateEdge(dba.get(), &u, &v, "EdgeType", {}, false);
@@ -462,10 +492,10 @@ TYPED_TEST(DumpTest, SingleEdge) {
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -480,7 +510,7 @@ TYPED_TEST(DumpTest, SingleEdge) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, MultipleEdges) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
auto u = CreateVertex(dba.get(), {}, {}, false);
auto v = CreateVertex(dba.get(), {}, {}, false);
auto w = CreateVertex(dba.get(), {}, {}, false);
@@ -491,10 +521,10 @@ TYPED_TEST(DumpTest, MultipleEdges) {
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -513,7 +543,7 @@ TYPED_TEST(DumpTest, MultipleEdges) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, EdgeWithProperties) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->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);
@@ -521,10 +551,10 @@ TYPED_TEST(DumpTest, EdgeWithProperties) {
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -539,22 +569,24 @@ TYPED_TEST(DumpTest, EdgeWithProperties) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, IndicesKeys) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {"Label1", "Label 2"}, {{"p", memgraph::storage::PropertyValue(1)}}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
ASSERT_FALSE(
this->context.db->CreateIndex(this->context.db->NameToLabel("Label1"), this->context.db->NameToProperty("prop"))
this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("Label1"), this->db->storage()->NameToProperty("prop"))
.HasError());
ASSERT_FALSE(
this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("Label 2"), this->db->storage()->NameToProperty("prop `"))
.HasError());
ASSERT_FALSE(this->context.db
->CreateIndex(this->context.db->NameToLabel("Label 2"), this->context.db->NameToProperty("prop `"))
.HasError());
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -567,21 +599,21 @@ TYPED_TEST(DumpTest, IndicesKeys) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, ExistenceConstraints) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {"L`abel 1"}, {{"prop", memgraph::storage::PropertyValue(1)}}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("L`abel 1"),
this->context.db->NameToProperty("prop"), {});
auto res = this->db->storage()->CreateExistenceConstraint(this->db->storage()->NameToLabel("L`abel 1"),
this->db->storage()->NameToProperty("prop"), {});
ASSERT_FALSE(res.HasError());
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -593,7 +625,7 @@ TYPED_TEST(DumpTest, ExistenceConstraints) {
TYPED_TEST(DumpTest, UniqueConstraints) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {"Label"},
{{"prop", memgraph::storage::PropertyValue(1)}, {"prop2", memgraph::storage::PropertyValue(2)}},
false);
@@ -603,18 +635,18 @@ TYPED_TEST(DumpTest, UniqueConstraints) {
ASSERT_FALSE(dba->Commit().HasError());
}
{
auto res = this->context.db->CreateUniqueConstraint(
this->context.db->NameToLabel("Label"),
{this->context.db->NameToProperty("prop"), this->context.db->NameToProperty("prop2")}, {});
auto res = this->db->storage()->CreateUniqueConstraint(
this->db->storage()->NameToLabel("Label"),
{this->db->storage()->NameToProperty("prop"), this->db->storage()->NameToProperty("prop2")}, {});
ASSERT_TRUE(res.HasValue());
ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
}
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -633,7 +665,7 @@ TYPED_TEST(DumpTest, UniqueConstraints) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
std::map<std::string, memgraph::storage::PropertyValue> prop1 = {
{"nested1", memgraph::storage::PropertyValue(1337)}, {"nested2", memgraph::storage::PropertyValue(3.14)}};
@@ -644,15 +676,30 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
ASSERT_FALSE(dba->Commit().HasError());
}
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);
memgraph::storage::Config config{};
config.durability.storage_directory = this->data_directory / "s1";
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s1").disk;
config.force_on_disk = true;
}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config);
auto db_acc_opt = db_gk.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db";
auto &db_acc = *db_acc_opt;
ASSERT_TRUE(db_acc->GetStorageMode() == (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -661,7 +708,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
for (const auto &item : results) {
ASSERT_EQ(item.size(), 1);
ASSERT_TRUE(item[0].IsString());
Execute(&interpreter_context, item[0].ValueString());
Execute(&interpreter_context, db_acc, item[0].ValueString());
}
}
}
@@ -669,7 +716,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->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(
@@ -710,33 +757,48 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
ASSERT_FALSE(dba->Commit().HasError());
}
{
auto ret = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("Person"),
this->context.db->NameToProperty("name"), {});
auto ret = this->db->storage()->CreateExistenceConstraint(this->db->storage()->NameToLabel("Person"),
this->db->storage()->NameToProperty("name"), {});
ASSERT_FALSE(ret.HasError());
}
{
auto ret = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("Person"),
{this->context.db->NameToProperty("name")}, {});
auto ret = this->db->storage()->CreateUniqueConstraint(this->db->storage()->NameToLabel("Person"),
{this->db->storage()->NameToProperty("name")}, {});
ASSERT_TRUE(ret.HasValue());
ASSERT_EQ(ret.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
}
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"))
ASSERT_FALSE(this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("Person"), this->db->storage()->NameToProperty("id"))
.HasError());
ASSERT_FALSE(this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("Person"),
this->db->storage()->NameToProperty("unexisting_property"))
.HasError());
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);
const auto &db_initial_state = GetState(this->db->storage());
memgraph::storage::Config config{};
config.durability.storage_directory = this->data_directory / "s2";
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s2").disk;
config.force_on_disk = true;
}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config);
auto db_acc_opt = db_gk.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db";
auto &db_acc = *db_acc_opt;
ASSERT_TRUE(db_acc->GetStorageMode() == (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
{
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
{
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream);
}
@@ -749,23 +811,23 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
ASSERT_EQ(item.size(), 1);
ASSERT_TRUE(item[0].IsString());
spdlog::debug("Query: {}", item[0].ValueString());
Execute(&interpreter_context, item[0].ValueString());
Execute(&interpreter_context, db_acc, item[0].ValueString());
++i;
}
}
ASSERT_EQ(GetState(this->context.db.get()), db_initial_state);
ASSERT_EQ(GetState(this->db->storage()), db_initial_state);
}
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, ExecuteDumpDatabase) {
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {}, {}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
{
auto stream = Execute(&this->context, "DUMP DATABASE");
auto stream = Execute(&this->context, this->db, "DUMP DATABASE");
const auto &header = stream.GetHeader();
const auto &results = stream.GetResults();
ASSERT_EQ(header.size(), 1U);
@@ -784,11 +846,11 @@ TYPED_TEST(DumpTest, ExecuteDumpDatabase) {
class StatefulInterpreter {
public:
explicit StatefulInterpreter(memgraph::query::InterpreterContext *context)
: context_(context), interpreter_(context_) {}
explicit StatefulInterpreter(memgraph::query::InterpreterContext *context, memgraph::dbms::DatabaseAccess db)
: context_(context), interpreter_(context_, db) {}
auto Execute(const std::string &query) {
ResultStreamFaker stream(context_->db.get());
ResultStreamFaker stream(interpreter_.db_acc_->get()->storage());
auto [header, _1, qid, _2] = interpreter_.Prepare(query, {}, nullptr);
stream.Header(header);
@@ -799,18 +861,13 @@ class StatefulInterpreter {
}
private:
static const std::filesystem::path data_directory_;
memgraph::query::InterpreterContext *context_;
memgraph::query::Interpreter interpreter_;
};
const std::filesystem::path StatefulInterpreter::data_directory_{std::filesystem::temp_directory_path() /
"MG_tests_unit_query_dump_stateful"};
// NOLINTNEXTLINE(hicpp-special-member-functions)
TYPED_TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) {
StatefulInterpreter interpreter(&this->context);
StatefulInterpreter interpreter(&this->context, this->db);
// Begin the transaction before the vertex is created.
interpreter.Execute("BEGIN");
@@ -827,7 +884,7 @@ TYPED_TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) {
// Create the vertex.
{
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
CreateVertex(dba.get(), {}, {}, false);
ASSERT_FALSE(dba->Commit().HasError());
}
@@ -875,39 +932,41 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
{
// Create indices
ASSERT_FALSE(
this->context.db->CreateIndex(this->context.db->NameToLabel("PERSON"), this->context.db->NameToProperty("name"))
this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("PERSON"), this->db->storage()->NameToProperty("name"))
.HasError());
ASSERT_FALSE(
this->db->storage()
->CreateIndex(this->db->storage()->NameToLabel("PERSON"), this->db->storage()->NameToProperty("surname"))
.HasError());
ASSERT_FALSE(this->context.db
->CreateIndex(this->context.db->NameToLabel("PERSON"), this->context.db->NameToProperty("surname"))
.HasError());
// Create existence constraints
{
auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("PERSON"),
this->context.db->NameToProperty("name"), {});
auto res = this->db->storage()->CreateExistenceConstraint(this->db->storage()->NameToLabel("PERSON"),
this->db->storage()->NameToProperty("name"), {});
ASSERT_FALSE(res.HasError());
}
{
auto res = this->context.db->CreateExistenceConstraint(this->context.db->NameToLabel("PERSON"),
this->context.db->NameToProperty("surname"), {});
auto res = this->db->storage()->CreateExistenceConstraint(this->db->storage()->NameToLabel("PERSON"),
this->db->storage()->NameToProperty("surname"), {});
ASSERT_FALSE(res.HasError());
}
// Create unique constraints
{
auto res = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("PERSON"),
{this->context.db->NameToProperty("name")}, {});
auto res = this->db->storage()->CreateUniqueConstraint(this->db->storage()->NameToLabel("PERSON"),
{this->db->storage()->NameToProperty("name")}, {});
ASSERT_TRUE(res.HasValue());
ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
}
{
auto res = this->context.db->CreateUniqueConstraint(this->context.db->NameToLabel("PERSON"),
{this->context.db->NameToProperty("surname")}, {});
auto res = this->db->storage()->CreateUniqueConstraint(this->db->storage()->NameToLabel("PERSON"),
{this->db->storage()->NameToProperty("surname")}, {});
ASSERT_TRUE(res.HasValue());
ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS);
}
auto dba = this->context.db->Access();
auto dba = this->db->storage()->Access();
auto p1 = CreateVertex(dba.get(), {"PERSON"},
{{"name", memgraph::storage::PropertyValue("Person1")},
{"surname", memgraph::storage::PropertyValue("Unique1")}},
@@ -935,9 +994,9 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) {
ASSERT_FALSE(dba->Commit().HasError());
}
ResultStreamFaker stream(this->context.db.get());
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());
auto acc = this->context.db->Access();
auto acc = this->db->storage()->Access();
memgraph::query::DbAccessor dba(acc.get());
memgraph::query::PullPlanDump pullPlan{&dba};

View File

@@ -23,6 +23,8 @@
#include "communication/result_stream_faker.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/stream/streams.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/storage.hpp"
@@ -32,24 +34,48 @@ template <typename StorageType>
class QueryExecution : public testing::Test {
protected:
const std::string testSuite = "query_plan_edge_cases";
std::optional<memgraph::dbms::DatabaseAccess> db_acc_;
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"};
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
void SetUp() {
interpreter_context_.emplace(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)),
memgraph::query::InterpreterConfig{}, data_directory);
interpreter_.emplace(&*interpreter_context_);
auto db_acc_opt = db_gk->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
db_acc_ = std::move(db_acc);
interpreter_context_.emplace(memgraph::query::InterpreterConfig{}, nullptr);
interpreter_.emplace(&*interpreter_context_, *db_acc_);
}
void TearDown() {
interpreter_ = std::nullopt;
interpreter_context_ = std::nullopt;
db_acc_.reset();
db_gk.reset();
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}
std::filesystem::remove_all(data_directory);
}
/**
@@ -58,7 +84,7 @@ class QueryExecution : public testing::Test {
* Return the query results.
*/
auto Execute(const std::string &query) {
ResultStreamFaker stream(this->interpreter_context_->db.get());
ResultStreamFaker stream(this->db_acc_->get()->storage());
auto [header, _1, qid, _2] = interpreter_->Prepare(query, {}, nullptr);
stream.Header(header);

View File

@@ -22,6 +22,7 @@
#include "kafka_mock.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/stream/streams.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
@@ -74,9 +75,32 @@ class StreamsTestFixture : 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_{
std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)),
memgraph::query::InterpreterConfig{}, data_directory_};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory_;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
memgraph::dbms::DatabaseAccess db_{
[&]() {
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context_{memgraph::query::InterpreterConfig{}, nullptr};
std::filesystem::path streams_data_directory_{data_directory_ / "separate-dir-for-test"};
std::optional<StreamsTest> proxyStreams_;
@@ -84,11 +108,12 @@ class StreamsTestFixture : public ::testing::Test {
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}
std::filesystem::remove_all(data_directory_);
}
void ResetStreamsObject() {
proxyStreams_.emplace();
proxyStreams_->streams_.emplace(&interpreter_context_, streams_data_directory_);
proxyStreams_->streams_.emplace(streams_data_directory_);
}
void CheckStreamStatus(const StreamCheckData &check_data) {
@@ -151,8 +176,8 @@ TYPED_TEST_CASE(StreamsTestFixture, StorageTypes);
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);
this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(
check_data.name, check_data.info, check_data.owner, this->db_, &this->interpreter_context_);
EXPECT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data));
EXPECT_NO_THROW(this->proxyStreams_->streams_->Start(check_data.name));
@@ -178,12 +203,12 @@ TYPED_TEST(StreamsTestFixture, SimpleStreamManagement) {
TYPED_TEST(StreamsTestFixture, CreateAlreadyExisting) {
auto stream_info = this->CreateDefaultStreamInfo();
auto stream_name = GetDefaultStreamName();
this->proxyStreams_->streams_->template 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, this->db_, &this->interpreter_context_);
try {
this->proxyStreams_->streams_->template 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, this->db_, &this->interpreter_context_);
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));
@@ -194,8 +219,8 @@ TYPED_TEST(StreamsTestFixture, DropNotExistingStream) {
const auto stream_info = this->CreateDefaultStreamInfo();
const auto stream_name = GetDefaultStreamName();
const std::string not_existing_stream_name{"ThisDoesn'tExists"};
this->proxyStreams_->streams_->template 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, this->db_, &this->interpreter_context_);
try {
this->proxyStreams_->streams_->Drop(not_existing_stream_name);
@@ -250,7 +275,7 @@ TYPED_TEST(StreamsTestFixture, RestoreStreams) {
// Reset the Streams object to trigger reloading
this->ResetStreamsObject();
EXPECT_TRUE(this->proxyStreams_->streams_->GetStreamInfo().empty());
this->proxyStreams_->streams_->RestoreStreams();
this->proxyStreams_->streams_->RestoreStreams(this->db_, &this->interpreter_context_);
EXPECT_EQ(stream_check_datas.size(), this->proxyStreams_->streams_->GetStreamInfo().size());
for (const auto &check_data : stream_check_datas) {
ASSERT_NO_FATAL_FAILURE(this->CheckStreamStatus(check_data));
@@ -258,12 +283,12 @@ TYPED_TEST(StreamsTestFixture, RestoreStreams) {
}
};
this->proxyStreams_->streams_->RestoreStreams();
this->proxyStreams_->streams_->RestoreStreams(this->db_, &this->interpreter_context_);
EXPECT_TRUE(this->proxyStreams_->streams_->GetStreamInfo().empty());
for (auto &check_data : stream_check_datas) {
this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(
check_data.name, check_data.info, check_data.owner);
check_data.name, check_data.info, check_data.owner, this->db_, &this->interpreter_context_);
}
{
SCOPED_TRACE("After streams are created");
@@ -299,13 +324,13 @@ TYPED_TEST(StreamsTestFixture, RestoreStreams) {
TYPED_TEST(StreamsTestFixture, CheckWithTimeout) {
const auto stream_info = this->CreateDefaultStreamInfo();
const auto stream_name = GetDefaultStreamName();
this->proxyStreams_->streams_->template 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, this->db_, &this->interpreter_context_);
std::chrono::milliseconds timeout{3000};
const auto start = std::chrono::steady_clock::now();
EXPECT_THROW(this->proxyStreams_->streams_->Check(stream_name, timeout, std::nullopt),
EXPECT_THROW(this->proxyStreams_->streams_->Check(stream_name, this->db_, timeout, std::nullopt),
memgraph::integrations::kafka::ConsumerCheckFailedException);
const auto end = std::chrono::steady_clock::now();
@@ -325,7 +350,7 @@ TYPED_TEST(StreamsTestFixture, CheckInvalidConfig) {
EXPECT_TRUE(message.find(kConfigValue) != std::string::npos) << message;
};
EXPECT_THROW_WITH_MSG(this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(
stream_name, stream_info, std::nullopt),
stream_name, stream_info, std::nullopt, this->db_, &this->interpreter_context_),
memgraph::integrations::kafka::SettingCustomConfigFailed, checker);
}
@@ -341,6 +366,6 @@ TYPED_TEST(StreamsTestFixture, CheckInvalidCredentials) {
EXPECT_TRUE(message.find(kCredentialValue) == std::string::npos) << message;
};
EXPECT_THROW_WITH_MSG(this->proxyStreams_->streams_->template Create<memgraph::query::stream::KafkaStream>(
stream_name, stream_info, std::nullopt),
stream_name, stream_info, std::nullopt, this->db_, &this->interpreter_context_),
memgraph::integrations::kafka::SettingCustomConfigFailed, checker);
}

View File

@@ -18,6 +18,7 @@
#include "interpreter_faker.hpp"
#include "query/exceptions.hpp"
#include "query/interpreter_context.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
#include "storage/v2/storage_mode.hpp"
@@ -68,10 +69,26 @@ INSTANTIATE_TEST_CASE_P(ParameterizedStorageModeTests, StorageModeTest, ::testin
class StorageModeMultiTxTest : public ::testing::Test {
protected:
std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_storage_mode"};
memgraph::query::InterpreterContext interpreter_context{
std::make_unique<memgraph::storage::InMemoryStorage>(), {}, data_directory};
InterpreterFaker running_interpreter{&interpreter_context}, main_interpreter{&interpreter_context};
std::filesystem::path data_directory = []() {
const auto tmp = std::filesystem::temp_directory_path() / "MG_tests_unit_storage_mode";
std::filesystem::remove_all(tmp);
return tmp;
}(); // iile
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{memgraph::storage::Config{
.durability.storage_directory = data_directory, .disk.main_storage_directory = data_directory / "disk"}};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc, "Failed to access db");
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
InterpreterFaker running_interpreter{&interpreter_context, db}, main_interpreter{&interpreter_context, db};
};
TEST_F(StorageModeMultiTxTest, ModeSwitchInactiveTransaction) {
@@ -87,11 +104,11 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchInactiveTransaction) {
while (!started) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
main_interpreter.Interpret("STORAGE MODE IN_MEMORY_ANALYTICAL");
// should change state
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
// finish thread
running_thread.request_stop();
@@ -100,7 +117,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchInactiveTransaction) {
TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) {
// transactional state
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
main_interpreter.Interpret("BEGIN");
bool started = false;
@@ -119,7 +136,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
// should not change still
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
main_interpreter.Interpret("COMMIT");
@@ -127,7 +144,7 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
// should change state
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
// finish thread
running_thread.request_stop();
@@ -135,11 +152,11 @@ TEST_F(StorageModeMultiTxTest, ModeSwitchActiveTransaction) {
}
TEST_F(StorageModeMultiTxTest, ErrorChangeIsolationLevel) {
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL);
main_interpreter.Interpret("STORAGE MODE IN_MEMORY_ANALYTICAL");
// should change state
ASSERT_EQ(interpreter_context.db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
ASSERT_EQ(db->GetStorageMode(), memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL);
ASSERT_THROW(running_interpreter.Interpret("SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;"),
memgraph::query::IsolationLevelModificationInAnalyticsException);

View File

@@ -19,6 +19,7 @@
#include "disk_test_utils.hpp"
#include "interpreter_faker.hpp"
#include "query/interpreter_context.hpp"
#include "storage/v2/inmemory/storage.hpp"
/*
@@ -30,11 +31,38 @@ class TransactionQueueSimpleTest : public ::testing::Test {
protected:
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{
std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {}, data_directory};
InterpreterFaker running_interpreter{&interpreter_context}, main_interpreter{&interpreter_context};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
void TearDown() override { disk_test_utils::RemoveRocksDbDirs(testSuite); }
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
InterpreterFaker running_interpreter{&interpreter_context, db}, main_interpreter{&interpreter_context, db};
void TearDown() override {
disk_test_utils::RemoveRocksDbDirs(testSuite);
std::filesystem::remove_all(data_directory);
}
};
using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>;

View File

@@ -22,6 +22,7 @@
#include "disk_test_utils.hpp"
#include "interpreter_faker.hpp"
#include "query/exceptions.hpp"
#include "query/interpreter_context.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
@@ -38,14 +39,39 @@ class TransactionQueueMultipleTest : public ::testing::Test {
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{
std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {}, data_directory};
InterpreterFaker main_interpreter{&interpreter_context};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
MG_ASSERT(db_acc->GetStorageMode() == (std::is_same_v<StorageType, memgraph::storage::DiskStorage>
? memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL),
"Wrong storage mode!");
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
InterpreterFaker main_interpreter{&interpreter_context, db};
std::vector<InterpreterFaker *> running_interpreters;
TransactionQueueMultipleTest() {
for (int i = 0; i < NUM_INTERPRETERS; ++i) {
InterpreterFaker *faker = new InterpreterFaker(&interpreter_context);
InterpreterFaker *faker = new InterpreterFaker(&interpreter_context, db);
running_interpreters.push_back(faker);
}
}
@@ -55,6 +81,7 @@ class TransactionQueueMultipleTest : public ::testing::Test {
delete running_interpreters[i];
}
disk_test_utils::RemoveRocksDbDirs(testSuite);
std::filesystem::remove_all(data_directory);
}
};

View File

@@ -1,296 +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 <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <thread>
#include <utils/sync_ptr.hpp>
#include "utils/exceptions.hpp"
using namespace std::chrono_literals;
TEST(SyncPtr, Basic) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test> sp(alive);
ASSERT_TRUE(alive);
auto sp_copy1 = sp.get();
auto sp_copy2 = sp.get();
sp_copy1.reset();
ASSERT_TRUE(alive);
sp_copy2.reset();
ASSERT_TRUE(alive);
sp.DestroyAndSync();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, BasicWConfig) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
struct TestConf {
TestConf(int i) : conf_(i) {}
int conf_;
};
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test, TestConf> sp(123, alive);
ASSERT_TRUE(alive);
ASSERT_EQ(sp.config().conf_, 123);
auto sp_copy1 = sp.get();
auto sp_copy2 = sp.get();
sp_copy1.reset();
ASSERT_TRUE(alive);
sp_copy2.reset();
ASSERT_TRUE(alive);
sp.DestroyAndSync();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, Sync) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test> sp(alive);
{
using namespace std::chrono_literals;
sp.timeout(10000ms); // 10sec
ASSERT_TRUE(alive);
auto sp_copy1 = sp.get();
auto sp_copy2 = sp.get();
th = std::thread([&alive, p = sp.get()]() mutable {
// Wait for a second and then release the pointer
// SyncPtr will be destroyed in the mean time (and block)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
ASSERT_TRUE(alive);
p.reset();
ASSERT_FALSE(alive);
});
}
ASSERT_TRUE(alive);
sp.DestroyAndSync();
th.join();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, SyncWConfig) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
struct TestConf {
TestConf(int i) : conf_(i) {}
int conf_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test, TestConf> sp(456, alive);
{
using namespace std::chrono_literals;
sp.timeout(10000ms); // 10sec
ASSERT_TRUE(alive);
ASSERT_EQ(sp.config().conf_, 456);
auto sp_copy1 = sp.get();
auto sp_copy2 = sp.get();
th = std::thread([&alive, p = sp.get()]() mutable {
// Wait for a second and then release the pointer
// SyncPtr will be destroyed in the mean time (and block)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
ASSERT_TRUE(alive);
p.reset();
ASSERT_FALSE(alive);
});
}
ASSERT_TRUE(alive);
sp.DestroyAndSync();
th.join();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, Timeout100ms) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test> sp(alive);
using namespace std::chrono_literals;
sp.timeout(100ms);
ASSERT_TRUE(alive);
auto p = sp.get();
ASSERT_TRUE(alive);
auto start_100ms = std::chrono::system_clock::now();
ASSERT_THROW(sp.DestroyAndSync(), memgraph::utils::BasicException);
auto end_100ms = std::chrono::system_clock::now();
auto delta_100ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_100ms - start_100ms).count();
ASSERT_NEAR(delta_100ms, 100, 100);
p.reset();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, Timeout567ms) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test> sp(alive);
using namespace std::chrono_literals;
sp.timeout(567ms);
ASSERT_TRUE(alive);
auto p = sp.get();
ASSERT_TRUE(alive);
auto start = std::chrono::system_clock::now();
ASSERT_THROW(sp.DestroyAndSync(), memgraph::utils::BasicException);
auto end = std::chrono::system_clock::now();
auto delta_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
ASSERT_NEAR(delta_ms, 567, 100);
p.reset();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, Timeout100msWConfig) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
struct TestConf {
TestConf(int i) : conf_(i) {}
int conf_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test, TestConf> sp(0, alive);
using namespace std::chrono_literals;
sp.timeout(100ms);
ASSERT_TRUE(alive);
auto p = sp.get();
ASSERT_TRUE(alive);
auto start_100ms = std::chrono::system_clock::now();
ASSERT_THROW(sp.DestroyAndSync(), memgraph::utils::BasicException);
auto end_100ms = std::chrono::system_clock::now();
auto delta_100ms = std::chrono::duration_cast<std::chrono::milliseconds>(end_100ms - start_100ms).count();
ASSERT_NEAR(delta_100ms, 100, 100);
p.reset();
ASSERT_FALSE(alive);
}
TEST(SyncPtr, Timeout567msWConfig) {
std::atomic_bool alive{false};
struct Test {
Test(std::atomic_bool &alive) : alive_(alive) { alive_ = true; }
~Test() { alive_ = false; }
std::atomic_bool &alive_;
};
struct TestConf {
TestConf(int i) : conf_(i) {}
int conf_;
};
std::thread th;
ASSERT_FALSE(alive);
memgraph::utils::SyncPtr<Test, TestConf> sp(2, alive);
using namespace std::chrono_literals;
sp.timeout(567ms);
ASSERT_TRUE(alive);
auto p = sp.get();
ASSERT_TRUE(alive);
auto start = std::chrono::system_clock::now();
ASSERT_THROW(sp.DestroyAndSync(), memgraph::utils::BasicException);
auto end = std::chrono::system_clock::now();
auto delta_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
ASSERT_NEAR(delta_ms, 567, 100);
p.reset();
ASSERT_FALSE(alive);
}