query/procedure: Add builtin mg.procedures

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic, ipaljak

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2579
This commit is contained in:
Teon Banek 2019-11-29 10:25:47 +01:00
parent 83c368c82b
commit 0d979dd7b3
3 changed files with 100 additions and 11 deletions

View File

@ -487,6 +487,19 @@ struct mgp_proc {
opt_args(memory),
results(memory) {}
/// @throw std::bad_alloc
/// @throw std::length_error
mgp_proc(const char *name,
std::function<void(const mgp_list *, const mgp_graph *, mgp_result *,
mgp_memory *)>
cb,
utils::MemoryResource *memory)
: name(name, memory),
cb(cb),
args(memory),
opt_args(memory),
results(memory) {}
/// @throw std::bad_alloc
/// @throw std::length_error
mgp_proc(const mgp_proc &other, utils::MemoryResource *memory)

View File

@ -74,12 +74,82 @@ bool CloseModule(Module *module) {
return true;
}
bool IsBuiltinModule(const Module &module) { return module.handle == nullptr; }
void RegisterMgProcedures(
// We expect modules to be sorted by name.
const std::map<std::string, Module, std::less<>> *all_modules,
Module *module) {
auto procedures_cb = [all_modules](const mgp_list *, const mgp_graph *,
mgp_result *result, mgp_memory *memory) {
// Iterating over all_modules assumes that the standard mechanism of custom
// procedure invocations takes the ModuleRegistry::lock_ with READ access.
// For details on how the invocation is done, take a look at the
// CallProcedureCursor::Pull implementation.
for (const auto &[module_name, module] : *all_modules) {
// Return the results in sorted order by module and by procedure.
static_assert(
std::is_same_v<decltype(module.procedures),
std::map<std::string, mgp_proc, std::less<>>>,
"Expected module procedures to be sorted by name");
for (const auto &[proc_name, proc] : module.procedures) {
auto *record = mgp_result_new_record(result);
if (!record) {
mgp_result_set_error_msg(result, "Not enough memory!");
return;
}
utils::pmr::string full_name(module_name, memory->impl);
full_name.append(1, '.');
full_name.append(proc_name);
auto *name_value = mgp_value_make_string(full_name.c_str(), memory);
if (!name_value) {
mgp_result_set_error_msg(result, "Not enough memory!");
return;
}
std::stringstream ss;
ss << module_name << ".";
PrintProcSignature(proc, &ss);
const auto signature = ss.str();
auto *signature_value =
mgp_value_make_string(signature.c_str(), memory);
if (!signature_value) {
mgp_value_destroy(name_value);
mgp_result_set_error_msg(result, "Not enough memory!");
return;
}
int succ1 = mgp_result_record_insert(record, "name", name_value);
int succ2 =
mgp_result_record_insert(record, "signature", signature_value);
mgp_value_destroy(name_value);
mgp_value_destroy(signature_value);
if (!succ1 || !succ2) {
mgp_result_set_error_msg(result, "Unable to set the result!");
return;
}
}
}
};
mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource());
mgp_proc_add_result(&procedures, "name", mgp_type_string());
mgp_proc_add_result(&procedures, "signature", mgp_type_string());
module->procedures.emplace("procedures", std::move(procedures));
}
} // namespace
ModuleRegistry::ModuleRegistry() {
Module module{.handle = nullptr};
RegisterMgProcedures(&modules_, &module);
modules_.emplace("mg", std::move(module));
}
bool ModuleRegistry::LoadModuleLibrary(std::filesystem::path path) {
std::unique_lock<utils::RWLock> guard(lock_);
std::string module_name(path.stem());
if (modules_.find(module_name) != modules_.end()) return true;
if (modules_.find(module_name) != modules_.end()) {
LOG(ERROR) << "Unable to overwrite an already loaded module " << path;
return false;
}
auto maybe_module = LoadModuleFromSharedLibrary(path);
if (!maybe_module) return false;
modules_[module_name] = std::move(*maybe_module);
@ -88,22 +158,21 @@ bool ModuleRegistry::LoadModuleLibrary(std::filesystem::path path) {
ModulePtr ModuleRegistry::GetModuleNamed(const std::string_view &name) {
std::shared_lock<utils::RWLock> guard(lock_);
// NOTE: std::unordered_map::find cannot work with std::string_view :(
auto found_it = modules_.find(std::string(name));
auto found_it = modules_.find(name);
if (found_it == modules_.end()) return nullptr;
return ModulePtr(&found_it->second, std::move(guard));
}
bool ModuleRegistry::ReloadModuleNamed(const std::string_view &name) {
std::unique_lock<utils::RWLock> guard(lock_);
// NOTE: std::unordered_map::find cannot work with std::string_view :(
auto found_it = modules_.find(std::string(name));
auto found_it = modules_.find(name);
if (found_it == modules_.end()) {
LOG(ERROR) << "Trying to reload module '" << name
<< "' which is not loaded.";
return false;
}
auto &module = found_it->second;
if (IsBuiltinModule(module)) return true;
if (!CloseModule(&module)) {
modules_.erase(found_it);
return false;
@ -120,6 +189,7 @@ bool ModuleRegistry::ReloadModuleNamed(const std::string_view &name) {
bool ModuleRegistry::ReloadAllModules() {
std::unique_lock<utils::RWLock> guard(lock_);
for (auto &[name, module] : modules_) {
if (IsBuiltinModule(module)) continue;
if (!CloseModule(&module)) {
modules_.erase(name);
return false;
@ -136,7 +206,10 @@ bool ModuleRegistry::ReloadAllModules() {
void ModuleRegistry::UnloadAllModules() {
std::unique_lock<utils::RWLock> guard(lock_);
for (auto &name_and_module : modules_) CloseModule(&name_and_module.second);
for (auto &name_and_module : modules_) {
if (IsBuiltinModule(name_and_module.second)) continue;
CloseModule(&name_and_module.second);
}
modules_.clear();
}

View File

@ -46,10 +46,12 @@ class ModulePtr final {
/// Thread-safe registration of modules from libraries, uses utils::RWLock.
class ModuleRegistry final {
std::unordered_map<std::string, Module> modules_;
std::map<std::string, Module, std::less<>> modules_;
utils::RWLock lock_{utils::RWLock::Priority::WRITE};
public:
ModuleRegistry();
/// Load a module from the given path and return true if successful.
///
/// A write lock is taken during the execution of this method. Loading a
@ -64,17 +66,18 @@ class ModuleRegistry final {
ModulePtr GetModuleNamed(const std::string_view &name);
/// Reload a module with given name and return true if successful.
/// Takes a write lock. If false was returned, then the module is no longer
/// registered.
/// Takes a write lock. Builtin modules cannot be reloaded, though true will
/// be returned if you try to do so. If false was returned, then the module is
/// no longer registered.
bool ReloadModuleNamed(const std::string_view &name);
/// Reload all loaded modules and return true if successful.
/// Reload all loaded (non-builtin) modules and return true if successful.
/// Takes a write lock. If false was returned, the module which failed to
/// reload is no longer registered. Remaining modules may or may not be
/// reloaded, but are valid and registered.
bool ReloadAllModules();
/// Remove all loaded modules.
/// Remove all loaded (non-builtin) modules.
/// Takes a write lock.
void UnloadAllModules();
};