diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index 161903335..c9f27da66 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -2898,7 +2898,7 @@ class LockPathQuery : public memgraph::query::Query { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } - enum class Action { LOCK_PATH, UNLOCK_PATH }; + enum class Action { LOCK_PATH, UNLOCK_PATH, STATUS }; LockPathQuery() = default; diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 82cae5f55..502d6ca5d 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -328,7 +328,9 @@ antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasC antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) { auto *lock_query = storage_->Create<LockPathQuery>(); - if (ctx->LOCK()) { + if (ctx->STATUS()) { + lock_query->action_ = LockPathQuery::Action::STATUS; + } else if (ctx->LOCK()) { lock_query->action_ = LockPathQuery::Action::LOCK_PATH; } else if (ctx->UNLOCK()) { lock_query->action_ = LockPathQuery::Action::UNLOCK_PATH; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index 1a7a7e803..9bb1bfabc 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -91,6 +91,7 @@ memgraphCypherKeyword : cypherKeyword | SNAPSHOT | START | STATS + | STATUS | STORAGE | STREAM | STREAMS @@ -335,7 +336,7 @@ dropReplica : DROP REPLICA replicaName ; showReplicas : SHOW REPLICAS ; -lockPathQuery : ( LOCK | UNLOCK ) DATA DIRECTORY ; +lockPathQuery : ( LOCK | UNLOCK ) DATA DIRECTORY | DATA DIRECTORY LOCK STATUS; freeMemoryQuery : FREE MEMORY ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 index 931960583..862682d6e 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 @@ -107,6 +107,7 @@ SNAPSHOT : S N A P S H O T ; START : S T A R T ; STATISTICS : S T A T I S T I C S ; STATS : S T A T S ; +STATUS : S T A T U S ; STOP : S T O P ; STORAGE : S T O R A G E; STORAGE_MODE : S T O R A G E UNDERSCORE MODE; diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp index 076fe1186..456c704e6 100644 --- a/src/query/frontend/stripped_lexer_constants.hpp +++ b/src/query/frontend/stripped_lexer_constants.hpp @@ -145,6 +145,7 @@ const trie::Trie kKeywords = {"union", "drop", "show", "stats", + "status", "unique", "explain", "profile", @@ -212,6 +213,10 @@ const trie::Trie kKeywords = {"union", "off", "in_memory_transactional", "in_memory_analytical", + "data", + "directory", + "lock", + "unlock" "build"}; // Unicode codepoints that are allowed at the start of the unescaped name. diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 8d58b3093..3e94fa0e8 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -1780,25 +1780,49 @@ PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_tr auto *lock_path_query = utils::Downcast<LockPathQuery>(parsed_query.query); - return PreparedQuery{{}, - std::move(parsed_query.required_privileges), - [interpreter_context, action = lock_path_query->action_]( - AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { - switch (action) { - case LockPathQuery::Action::LOCK_PATH: - if (!interpreter_context->db->LockPath()) { - throw QueryRuntimeException("Failed to lock the data directory"); - } - break; - case LockPathQuery::Action::UNLOCK_PATH: - if (!interpreter_context->db->UnlockPath()) { - throw QueryRuntimeException("Failed to unlock the data directory"); - } - break; - } - return QueryHandlerResult::COMMIT; - }, - RWType::NONE}; + return PreparedQuery{ + {"STATUS"}, + std::move(parsed_query.required_privileges), + [interpreter_context, action = lock_path_query->action_]( + AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> { + std::vector<std::vector<TypedValue>> status; + std::string res; + + switch (action) { + case LockPathQuery::Action::LOCK_PATH: { + const auto lock_success = interpreter_context->db->LockPath(); + if (lock_success.HasError()) [[unlikely]] { + throw QueryRuntimeException("Failed to lock the data directory"); + } + res = lock_success.GetValue() ? "Data directory is now locked." : "Data directory is already locked."; + break; + } + case LockPathQuery::Action::UNLOCK_PATH: { + const auto unlock_success = interpreter_context->db->UnlockPath(); + if (unlock_success.HasError()) [[unlikely]] { + throw QueryRuntimeException("Failed to unlock the data directory"); + } + res = unlock_success.GetValue() ? "Data directory is now unlocked." : "Data directory is already unlocked."; + break; + } + case LockPathQuery::Action::STATUS: { + const auto locked_status = interpreter_context->db->IsPathLocked(); + if (locked_status.HasError()) [[unlikely]] { + throw QueryRuntimeException("Failed to access the data directory"); + } + res = locked_status.GetValue() ? "Data directory is locked." : "Data directory is unlocked."; + break; + } + } + + status.emplace_back(std::vector<TypedValue>{TypedValue(res)}); + auto pull_plan = std::make_shared<PullPlanVector>(std::move(status)); + if (pull_plan->Pull(stream, n)) { + return QueryHandlerResult::COMMIT; + } + return std::nullopt; + }, + RWType::NONE}; } PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, bool in_explicit_transaction, diff --git a/src/storage/v2/replication/replication_client.cpp b/src/storage/v2/replication/replication_client.cpp index e458a0f4d..310b3d912 100644 --- a/src/storage/v2/replication/replication_client.cpp +++ b/src/storage/v2/replication/replication_client.cpp @@ -399,7 +399,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient // we cannot know if the difference is only in the current WAL or we need // to send the snapshot. if (latest_snapshot) { - locker_acc.AddPath(latest_snapshot->path); + const auto lock_success = locker_acc.AddPath(latest_snapshot->path); + MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path."); recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path)); } // if there are no finalized WAL files, snapshot left the current WAL @@ -446,7 +447,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient // We need to lock these files and add them to the chain for (auto result_wal_it = wal_files->begin() + distance_from_first; result_wal_it != wal_files->end(); ++result_wal_it) { - locker_acc.AddPath(result_wal_it->path); + const auto lock_success = locker_acc.AddPath(result_wal_it->path); + MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path."); wal_chain.push_back(std::move(result_wal_it->path)); } @@ -464,7 +466,8 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient MG_ASSERT(latest_snapshot, "Invalid durability state, missing snapshot"); // We didn't manage to find a WAL chain, we need to send the latest snapshot // with its WALs - locker_acc.AddPath(latest_snapshot->path); + const auto lock_success = locker_acc.AddPath(latest_snapshot->path); + MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path."); recovery_steps.emplace_back(std::in_place_type_t<RecoverySnapshot>{}, std::move(latest_snapshot->path)); std::vector<std::filesystem::path> recovery_wal_files; @@ -483,13 +486,15 @@ std::vector<Storage::ReplicationClient::RecoveryStep> Storage::ReplicationClient } for (; wal_it != wal_files->end(); ++wal_it) { - locker_acc.AddPath(wal_it->path); + const auto lock_success = locker_acc.AddPath(wal_it->path); + MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path."); recovery_wal_files.push_back(std::move(wal_it->path)); } // We only have a WAL before the snapshot if (recovery_wal_files.empty()) { - locker_acc.AddPath(wal_files->back().path); + const auto lock_success = locker_acc.AddPath(wal_files->back().path); + MG_ASSERT(!lock_success.HasError(), "Tried to lock a nonexistant path."); recovery_wal_files.push_back(std::move(wal_files->back().path)); } diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 4ebc144c0..f558cc5ac 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -1980,16 +1980,23 @@ utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot(std::op return CreateSnapshotError::ReachedMaxNumTries; } -bool Storage::LockPath() { +utils::FileRetainer::FileLockerAccessor::ret_type Storage::IsPathLocked() { + auto locker_accessor = global_locker_.Access(); + return locker_accessor.IsPathLocked(config_.durability.storage_directory); +} + +utils::FileRetainer::FileLockerAccessor::ret_type Storage::LockPath() { auto locker_accessor = global_locker_.Access(); return locker_accessor.AddPath(config_.durability.storage_directory); } -bool Storage::UnlockPath() { +utils::FileRetainer::FileLockerAccessor::ret_type Storage::UnlockPath() { { auto locker_accessor = global_locker_.Access(); - if (!locker_accessor.RemovePath(config_.durability.storage_directory)) { - return false; + const auto ret = locker_accessor.RemovePath(config_.durability.storage_directory); + if (ret.HasError() || !ret.GetValue()) { + // Exit without cleaning the queue + return ret; } } diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 60541ed63..7fd816530 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -39,6 +39,7 @@ #include "storage/v2/vertex_accessor.hpp" #include "utils/file_locker.hpp" #include "utils/on_scope_exit.hpp" +#include "utils/result.hpp" #include "utils/rw_lock.hpp" #include "utils/scheduler.hpp" #include "utils/skip_list.hpp" @@ -467,8 +468,9 @@ class Storage final { StorageInfo GetInfo() const; - bool LockPath(); - bool UnlockPath(); + utils::FileRetainer::FileLockerAccessor::ret_type IsPathLocked(); + utils::FileRetainer::FileLockerAccessor::ret_type LockPath(); + utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath(); bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {}); diff --git a/src/utils/file_locker.cpp b/src/utils/file_locker.cpp index 7e8b6f6f8..39af7b9d9 100644 --- a/src/utils/file_locker.cpp +++ b/src/utils/file_locker.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -80,13 +80,14 @@ void FileRetainer::CleanQueue() { } ////// LockerEntry ////// -void FileRetainer::LockerEntry::LockPath(const std::filesystem::path &path) { +bool FileRetainer::LockerEntry::LockPath(const std::filesystem::path &path) { auto absolute_path = std::filesystem::absolute(path); if (std::filesystem::is_directory(absolute_path)) { - directories_.emplace(std::move(absolute_path)); - return; + const auto [itr, success] = directories_.emplace(std::move(absolute_path)); + return success; } - files_.emplace(std::move(absolute_path)); + const auto [itr, success] = files_.emplace(std::move(absolute_path)); + return success; } bool FileRetainer::LockerEntry::RemovePath(const std::filesystem::path &path) { @@ -140,13 +141,27 @@ FileRetainer::FileLockerAccessor::FileLockerAccessor(FileRetainer *retainer, siz file_retainer_->active_accessors_.fetch_add(1); } -bool FileRetainer::FileLockerAccessor::AddPath(const std::filesystem::path &path) { - if (!std::filesystem::exists(path)) return false; - file_retainer_->lockers_.WithLock([&](auto &lockers) { lockers[locker_id_].LockPath(path); }); - return true; +FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::IsPathLocked( + const std::filesystem::path &path) { + if (!std::filesystem::exists(path)) { + return Error::NonexistentPath; + } + return file_retainer_->FileLocked(std::filesystem::absolute(path)); } -bool FileRetainer::FileLockerAccessor::RemovePath(const std::filesystem::path &path) { +FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::AddPath( + const std::filesystem::path &path) { + if (!std::filesystem::exists(path)) { + return Error::NonexistentPath; + } + return file_retainer_->lockers_.WithLock([&](auto &lockers) { return lockers[locker_id_].LockPath(path); }); +} + +FileRetainer::FileLockerAccessor::ret_type FileRetainer::FileLockerAccessor::RemovePath( + const std::filesystem::path &path) { + if (!std::filesystem::exists(path)) { + return Error::NonexistentPath; + } return file_retainer_->lockers_.WithLock([&](auto &lockers) { return lockers[locker_id_].RemovePath(path); }); } diff --git a/src/utils/file_locker.hpp b/src/utils/file_locker.hpp index 1134bd73e..5fd96e159 100644 --- a/src/utils/file_locker.hpp +++ b/src/utils/file_locker.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,6 +18,7 @@ #include <unordered_map> #include "utils/file.hpp" +#include "utils/result.hpp" #include "utils/rw_lock.hpp" #include "utils/spin_lock.hpp" #include "utils/synchronized.hpp" @@ -114,15 +115,26 @@ class FileRetainer { struct FileLockerAccessor { friend FileLocker; + enum class Error : uint8_t { + NonexistentPath = 0, + }; + + using ret_type = utils::BasicResult<FileRetainer::FileLockerAccessor::Error, bool>; + + /** + * Checks if a single path is in the current locker. + */ + ret_type IsPathLocked(const std::filesystem::path &path); + /** * Add a single path to the current locker. */ - bool AddPath(const std::filesystem::path &path); + ret_type AddPath(const std::filesystem::path &path); /** * Remove a single path form the current locker. */ - bool RemovePath(const std::filesystem::path &path); + ret_type RemovePath(const std::filesystem::path &path); FileLockerAccessor(const FileLockerAccessor &) = delete; FileLockerAccessor(FileLockerAccessor &&) = default; @@ -182,7 +194,7 @@ class FileRetainer { class LockerEntry { public: - void LockPath(const std::filesystem::path &path); + bool LockPath(const std::filesystem::path &path); bool RemovePath(const std::filesystem::path &path); [[nodiscard]] bool LocksFile(const std::filesystem::path &path) const; diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index fcd0e5d74..c87e000b6 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -3368,10 +3368,43 @@ TEST_P(CypherMainVisitorTest, TestLockPathQuery) { ASSERT_TRUE(parsed_query); EXPECT_EQ(parsed_query->action_, action); } + + { + const std::string query = fmt::format("{} DATA DIRECTORY LOCK STATUS", command); + ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException); + } + + { + const std::string query = fmt::format("{} DATA DIRECTORY STATUS", command); + ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException); + } }; test_lock_path_query("LOCK", LockPathQuery::Action::LOCK_PATH); test_lock_path_query("UNLOCK", LockPathQuery::Action::UNLOCK_PATH); + + // Status test + { + const std::string query = "DATA DIRECTORY LOCK"; + ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException); + } + + { + const std::string query = "DATA LOCK STATUS"; + ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException); + } + + { + const std::string query = "DIRECTORY LOCK STATUS"; + ASSERT_THROW(ast_generator.ParseQuery(query), SyntaxException); + } + + { + const std::string query = "DATA DIRECTORY LOCK STATUS"; + auto *parsed_query = dynamic_cast<LockPathQuery *>(ast_generator.ParseQuery(query)); + ASSERT_TRUE(parsed_query); + EXPECT_EQ(parsed_query->action_, LockPathQuery::Action::STATUS); + } } TEST_P(CypherMainVisitorTest, TestLoadCsvClause) { diff --git a/tests/unit/utils_file_locker.cpp b/tests/unit/utils_file_locker.cpp index 21c71b1a3..bd0778d49 100644 --- a/tests/unit/utils_file_locker.cpp +++ b/tests/unit/utils_file_locker.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -89,7 +89,8 @@ TEST_P(FileLockerParameterizedTest, DeleteWhileInLocker) { auto locker = file_retainer.AddLocker(); { auto acc = locker.Access(); - acc.AddPath(lock_absolute ? file_absolute : file); + const auto lock_success = acc.AddPath(lock_absolute ? file_absolute : file); + ASSERT_FALSE(lock_success.HasError()); } file_retainer.DeleteFile(delete_absolute ? file_absolute : file); @@ -133,7 +134,9 @@ TEST_P(FileLockerParameterizedTest, DirectoryLock) { auto locker = file_retainer.AddLocker(); { auto acc = locker.Access(); - acc.AddPath(lock_absolute ? std::filesystem::absolute(directory_to_lock) : directory_to_lock); + const auto lock_success = + acc.AddPath(lock_absolute ? std::filesystem::absolute(directory_to_lock) : directory_to_lock); + ASSERT_FALSE(lock_success.HasError()); } file_retainer.DeleteFile(delete_absolute ? file_absolute : file); @@ -165,7 +168,8 @@ TEST_P(FileLockerParameterizedTest, RemovePath) { auto locker = file_retainer.AddLocker(); { auto acc = locker.Access(); - acc.AddPath(lock_absolute ? file_absolute : file); + const auto lock_success = acc.AddPath(lock_absolute ? file_absolute : file); + ASSERT_FALSE(lock_success.HasError()); } file_retainer.DeleteFile(delete_absolute ? file_absolute : file); @@ -205,8 +209,10 @@ TEST_F(FileLockerTest, MultipleLockers) { auto locker = file_retainer.AddLocker(); { auto acc = locker.Access(); - acc.AddPath(file1); - acc.AddPath(common_file); + const auto lock_success1 = acc.AddPath(file1); + ASSERT_FALSE(lock_success1.HasError()); + const auto lock_success2 = acc.AddPath(common_file); + ASSERT_FALSE(lock_success2.HasError()); } std::this_thread::sleep_for(200ms); }); @@ -215,8 +221,10 @@ TEST_F(FileLockerTest, MultipleLockers) { auto locker = file_retainer.AddLocker(); { auto acc = locker.Access(); - acc.AddPath(file2); - acc.AddPath(common_file); + const auto lock_success1 = acc.AddPath(file2); + ASSERT_FALSE(lock_success1.HasError()); + const auto lock_success2 = acc.AddPath(common_file); + ASSERT_FALSE(lock_success2.HasError()); } std::this_thread::sleep_for(200ms); }); @@ -275,7 +283,8 @@ TEST_F(FileLockerTest, MultipleLockersAndDeleters) { auto acc = locker.Access(); for (auto i = 0; i < file_access_num; ++i) { auto file = random_file(); - if (acc.AddPath(file)) { + const auto res = acc.AddPath(file); + if (!res.HasError()) { ASSERT_TRUE(std::filesystem::exists(file)); locked_files.emplace_back(std::move(file)); } else {