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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
224
tests/unit/dbms_database.cpp
Normal file
224
tests/unit/dbms_database.cpp
Normal 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
193
tests/unit/dbms_handler.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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> ¶ms = {}) {
|
||||
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));
|
||||
|
||||
@@ -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> ¶ms = {}) {
|
||||
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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user