Compare commits
29 Commits
T0985-MG-g
...
T0993-MG-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ce8098a71 | ||
|
|
f625f2c21a | ||
|
|
b085532425 | ||
|
|
f385e080ab | ||
|
|
633214baa8 | ||
|
|
fc10f5676e | ||
|
|
987cfe2c6d | ||
|
|
67ea684d1d | ||
|
|
e5a04489b1 | ||
|
|
480df4ed69 | ||
|
|
65ef870e17 | ||
|
|
2e0ae9153f | ||
|
|
3843366e7b | ||
|
|
80e0e439b7 | ||
|
|
351258ace8 | ||
|
|
74d3663821 | ||
|
|
c830bc7d81 | ||
|
|
ca6ee0c209 | ||
|
|
17cb59d75a | ||
|
|
5e87ce9f65 | ||
|
|
a2643cc133 | ||
|
|
f85ee31b4b | ||
|
|
5914b97f5e | ||
|
|
019f226b5e | ||
|
|
92b4e39b21 | ||
|
|
5f8ae644ff | ||
|
|
761f536b75 | ||
|
|
eb0b3141d5 | ||
|
|
ff2f8031a9 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -3,7 +3,9 @@
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Write E2E tests
|
||||
- [ ] Compare the [benchmarking results](https://bench-graph.memgraph.com/) between the master branch and the Epic branch
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
[master < Task] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
17
.github/workflows/diff.yaml
vendored
17
.github/workflows/diff.yaml
vendored
@@ -70,6 +70,11 @@ jobs:
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
# This is also needed if we want do to comparison against other branches
|
||||
# See https://github.community/t/checkout-code-fails-when-it-runs-lerna-run-test-since-master/17920
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: git fetch
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -110,12 +115,22 @@ jobs:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
- name: Set base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set base branch # if we manually dispatch or push to master
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 master... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
|
||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
|
||||
|
||||
# Fail if any warning is reported
|
||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||
|
||||
1
libs/.gitignore
vendored
1
libs/.gitignore
vendored
@@ -5,3 +5,4 @@
|
||||
!CMakeLists.txt
|
||||
!__main.cpp
|
||||
!pulsar.patch
|
||||
!antlr4.10.1.patch
|
||||
|
||||
@@ -106,6 +106,7 @@ import_external_library(antlr4 STATIC
|
||||
-DWITH_LIBCXX=OFF # because of debian bug
|
||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
-DANTLR_BUILD_CPP_TESTS=OFF
|
||||
BUILD_COMMAND $(MAKE) antlr4_static
|
||||
INSTALL_COMMAND $(MAKE) install)
|
||||
|
||||
|
||||
13
libs/antlr4.10.1.patch
Normal file
13
libs/antlr4.10.1.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/runtime/Cpp/runtime/CMakeLists.txt b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
index baf46cac9..2e7756de8 100644
|
||||
--- a/runtime/Cpp/runtime/CMakeLists.txt
|
||||
+++ b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
@@ -134,7 +134,7 @@ set_target_properties(antlr4_static
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${LIB_OUTPUT_DIR}
|
||||
COMPILE_FLAGS "${disabled_compile_warnings} ${extra_static_compile_flags}")
|
||||
|
||||
-install(TARGETS antlr4_shared
|
||||
+install(TARGETS antlr4_shared OPTIONAL
|
||||
EXPORT antlr4-targets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
@@ -1,43 +0,0 @@
|
||||
diff --git a/runtime/Cpp/runtime/CMakeLists.txt b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
index a8503bb..11362cf 100644
|
||||
--- a/runtime/Cpp/runtime/CMakeLists.txt
|
||||
+++ b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
@@ -5,8 +5,8 @@ set(THIRDPARTY_DIR ${CMAKE_BINARY_DIR}/runtime/thirdparty)
|
||||
set(UTFCPP_DIR ${THIRDPARTY_DIR}/utfcpp)
|
||||
ExternalProject_Add(
|
||||
utfcpp
|
||||
- GIT_REPOSITORY "git://github.com/nemtrif/utfcpp"
|
||||
- GIT_TAG "v3.1.1"
|
||||
+ GIT_REPOSITORY "https://github.com/nemtrif/utfcpp"
|
||||
+ GIT_TAG "v3.2.1"
|
||||
SOURCE_DIR ${UTFCPP_DIR}
|
||||
UPDATE_DISCONNECTED 1
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${UTFCPP_DIR}/install -Dgtest_force_shared_crt=ON
|
||||
@@ -118,7 +118,7 @@ set_target_properties(antlr4_static
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${LIB_OUTPUT_DIR}
|
||||
COMPILE_FLAGS "${disabled_compile_warnings} ${extra_static_compile_flags}")
|
||||
|
||||
-install(TARGETS antlr4_shared
|
||||
+install(TARGETS antlr4_shared OPTIONAL
|
||||
DESTINATION lib
|
||||
EXPORT antlr4-targets)
|
||||
install(TARGETS antlr4_static
|
||||
diff --git a/runtime/Cpp/runtime/src/support/Any.h b/runtime/Cpp/runtime/src/support/Any.h
|
||||
index 468db98..65a473b 100644
|
||||
--- a/runtime/Cpp/runtime/src/support/Any.h
|
||||
+++ b/runtime/Cpp/runtime/src/support/Any.h
|
||||
@@ -122,12 +122,12 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
- template<int N = 0, typename std::enable_if<N == N && std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
|
||||
+ template<int N = 0, typename std::enable_if<N == N && std::is_copy_constructible<T>::value, int>::type = 0>
|
||||
Base* clone() const {
|
||||
return new Derived<T>(value);
|
||||
}
|
||||
|
||||
- template<int N = 0, typename std::enable_if<N == N && !std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
|
||||
+ template<int N = 0, typename std::enable_if<N == N && !std::is_copy_constructible<T>::value, int>::type = 0>
|
||||
Base* clone() const {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ repo_clone_try_double () {
|
||||
# Download from primary_urls might fail because the cache is not installed.
|
||||
declare -A primary_urls=(
|
||||
["antlr4-code"]="http://$local_cache_host/git/antlr4.git"
|
||||
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.9.2-complete.jar"
|
||||
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.10.1-complete.jar"
|
||||
["cppitertools"]="http://$local_cache_host/git/cppitertools.git"
|
||||
["rapidcheck"]="http://$local_cache_host/git/rapidcheck.git"
|
||||
["gbenchmark"]="http://$local_cache_host/git/benchmark.git"
|
||||
@@ -130,7 +130,7 @@ declare -A primary_urls=(
|
||||
# should fail.
|
||||
declare -A secondary_urls=(
|
||||
["antlr4-code"]="https://github.com/antlr/antlr4.git"
|
||||
["antlr4-generator"]="http://www.antlr.org/download/antlr-4.9.2-complete.jar"
|
||||
["antlr4-generator"]="https://www.antlr.org/download/antlr-4.10.1-complete.jar"
|
||||
["cppitertools"]="https://github.com/ryanhaining/cppitertools.git"
|
||||
["rapidcheck"]="https://github.com/emil-e/rapidcheck.git"
|
||||
["gbenchmark"]="https://github.com/google/benchmark.git"
|
||||
@@ -152,10 +152,10 @@ declare -A secondary_urls=(
|
||||
# antlr
|
||||
file_get_try_double "${primary_urls[antlr4-generator]}" "${secondary_urls[antlr4-generator]}"
|
||||
|
||||
antlr4_tag="4.9.2" # v4.9.2
|
||||
antlr4_tag="4.10.1" # v4.10.1
|
||||
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag" true
|
||||
pushd antlr4
|
||||
git apply ../antlr4.patch
|
||||
git apply ../antlr4.10.1.patch
|
||||
popd
|
||||
|
||||
# cppitertools v2.0 2019-12-23
|
||||
@@ -199,7 +199,7 @@ git apply ../rocksdb.patch
|
||||
popd
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="96e95c6845463cbe88948392be58d26da0d5ffd3" # (2022-02-08)
|
||||
mgclient_tag="v1.4.0" # (2022-06-14)
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
|
||||
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
namespace memgraph::auth {
|
||||
/**
|
||||
* This class serves as the main Authentication/Authorization storage.
|
||||
* It provides functions for managing Users, Roles and Permissions.
|
||||
* It provides functions for managing Users, Roles, Permissions and FineGrainedAccessPermissions.
|
||||
* NOTE: The non-const functions in this class aren't thread safe.
|
||||
* TODO (mferencevic): Disable user/role modification functions when they are
|
||||
* being managed by the auth module.
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
#include "auth/models.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
@@ -98,12 +101,7 @@ std::string PermissionLevelToString(PermissionLevel level) {
|
||||
}
|
||||
}
|
||||
|
||||
Permissions::Permissions(uint64_t grants, uint64_t denies) {
|
||||
// The deny bitmask has higher priority than the grant bitmask.
|
||||
denies_ = denies;
|
||||
// Mask out the grant bitmask to make sure that it is correct.
|
||||
grants_ = grants & (~denies);
|
||||
}
|
||||
Permissions::Permissions(uint64_t grants, uint64_t denies) : grants_(grants & (~denies)), denies_(denies) {}
|
||||
|
||||
PermissionLevel Permissions::Has(Permission permission) const {
|
||||
// Check for the deny first because it has greater priority than a grant.
|
||||
@@ -183,19 +181,176 @@ bool operator==(const Permissions &first, const Permissions &second) {
|
||||
|
||||
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
|
||||
|
||||
const std::string ASTERISK = "*";
|
||||
|
||||
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants,
|
||||
const std::unordered_set<std::string> &denies)
|
||||
: grants_(grants), denies_(denies) {}
|
||||
|
||||
PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission) const {
|
||||
if ((denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) || denies_.find(permission) != denies_.end()) {
|
||||
return PermissionLevel::DENY;
|
||||
}
|
||||
|
||||
if ((grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) || grants_.find(permission) != denies_.end()) {
|
||||
return PermissionLevel::GRANT;
|
||||
}
|
||||
|
||||
return PermissionLevel::NEUTRAL;
|
||||
}
|
||||
|
||||
void FineGrainedAccessPermissions::Grant(const std::string &permission) {
|
||||
if (permission == ASTERISK) {
|
||||
grants_.clear();
|
||||
grants_.insert(permission);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto deniedPermissionIter = denies_.find(permission);
|
||||
|
||||
if (deniedPermissionIter != denies_.end()) {
|
||||
denies_.erase(deniedPermissionIter);
|
||||
}
|
||||
|
||||
if (grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) {
|
||||
grants_.erase(ASTERISK);
|
||||
}
|
||||
|
||||
if (grants_.find(permission) == grants_.end()) {
|
||||
grants_.insert(permission);
|
||||
}
|
||||
}
|
||||
|
||||
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
|
||||
if (permission == ASTERISK) {
|
||||
grants_.clear();
|
||||
denies_.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto deniedPermissionIter = denies_.find(permission);
|
||||
auto grantedPermissionIter = grants_.find(permission);
|
||||
|
||||
if (deniedPermissionIter != denies_.end()) {
|
||||
denies_.erase(deniedPermissionIter);
|
||||
}
|
||||
|
||||
if (grantedPermissionIter != grants_.end()) {
|
||||
grants_.erase(grantedPermissionIter);
|
||||
}
|
||||
}
|
||||
|
||||
void FineGrainedAccessPermissions::Deny(const std::string &permission) {
|
||||
if (permission == ASTERISK) {
|
||||
denies_.clear();
|
||||
denies_.insert(permission);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto grantedPermissionIter = grants_.find(permission);
|
||||
|
||||
if (grantedPermissionIter != grants_.end()) {
|
||||
grants_.erase(grantedPermissionIter);
|
||||
}
|
||||
|
||||
if (denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) {
|
||||
denies_.erase(ASTERISK);
|
||||
}
|
||||
|
||||
if (denies_.find(permission) == denies_.end()) {
|
||||
denies_.insert(permission);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["grants"] = grants_;
|
||||
data["denies"] = denies_;
|
||||
return data;
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
|
||||
return FineGrainedAccessPermissions(data["grants"], data["denies"]);
|
||||
}
|
||||
|
||||
const std::unordered_set<std::string> &FineGrainedAccessPermissions::grants() const { return grants_; }
|
||||
const std::unordered_set<std::string> &FineGrainedAccessPermissions::denies() const { return denies_; }
|
||||
|
||||
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
|
||||
return first.grants() == second.grants() && first.denies() == second.denies();
|
||||
}
|
||||
|
||||
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
FineGrainedAccessHandler::FineGrainedAccessHandler(const FineGrainedAccessPermissions &labelPermissions,
|
||||
const FineGrainedAccessPermissions &edgeTypePermissions)
|
||||
: label_permissions_(labelPermissions), edge_type_permissions_(edgeTypePermissions) {}
|
||||
|
||||
const FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() const { return label_permissions_; }
|
||||
FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() { return label_permissions_; }
|
||||
|
||||
const FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() const {
|
||||
return edge_type_permissions_;
|
||||
}
|
||||
FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() { return edge_type_permissions_; }
|
||||
|
||||
nlohmann::json FineGrainedAccessHandler::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["label_permissions"] = label_permissions_.Serialize();
|
||||
data["edge_type_permissions"] = edge_type_permissions_.Serialize();
|
||||
return data;
|
||||
}
|
||||
|
||||
FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
if (!data["label_permissions"].is_object() && !data["edge_type_permissions"].is_object()) {
|
||||
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
|
||||
}
|
||||
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
|
||||
auto edge_type_permissions = FineGrainedAccessPermissions::Deserialize(data["edge_type_permissions"]);
|
||||
|
||||
return FineGrainedAccessHandler(label_permissions, edge_type_permissions);
|
||||
}
|
||||
|
||||
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
|
||||
return first.label_permissions_ == second.label_permissions_ &&
|
||||
first.edge_type_permissions_ == second.edge_type_permissions_;
|
||||
}
|
||||
|
||||
bool operator!=(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
|
||||
|
||||
Role::Role(const std::string &rolename, const Permissions &permissions)
|
||||
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
|
||||
Role::Role(const std::string &rolename, const Permissions &permissions,
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler)
|
||||
: rolename_(utils::ToLowerCase(rolename)),
|
||||
permissions_(permissions),
|
||||
fine_grained_access_handler_(fine_grained_access_handler) {}
|
||||
|
||||
const std::string &Role::rolename() const { return rolename_; }
|
||||
const Permissions &Role::permissions() const { return permissions_; }
|
||||
Permissions &Role::permissions() { return permissions_; }
|
||||
const FineGrainedAccessHandler &Role::fine_grained_access_handler() const { return fine_grained_access_handler_; }
|
||||
FineGrainedAccessHandler &Role::fine_grained_access_handler() { return fine_grained_access_handler_; }
|
||||
|
||||
nlohmann::json Role::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["rolename"] = rolename_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -203,21 +358,28 @@ Role Role::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
|
||||
if (!data["rolename"].is_string() || !data["permissions"].is_object() ||
|
||||
!data["fine_grained_access_handler"].is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
return {data["rolename"], permissions};
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
return {data["rolename"], permissions, fine_grained_access_handler};
|
||||
}
|
||||
|
||||
bool operator==(const Role &first, const Role &second) {
|
||||
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
|
||||
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_ &&
|
||||
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
|
||||
}
|
||||
|
||||
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
||||
|
||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
|
||||
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
|
||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler)
|
||||
: username_(utils::ToLowerCase(username)),
|
||||
password_hash_(password_hash),
|
||||
permissions_(permissions),
|
||||
fine_grained_access_handler_(fine_grained_access_handler) {}
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
if (password_hash_.empty()) return true;
|
||||
@@ -260,16 +422,64 @@ void User::ClearRole() { role_ = std::nullopt; }
|
||||
|
||||
Permissions User::GetPermissions() const {
|
||||
if (role_) {
|
||||
return Permissions(permissions_.grants() | role_->permissions().grants(),
|
||||
permissions_.denies() | role_->permissions().denies());
|
||||
return {permissions_.grants() | role_->permissions().grants(),
|
||||
permissions_.denies() | role_->permissions().denies()};
|
||||
}
|
||||
return permissions_;
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
|
||||
if (role_) {
|
||||
std::unordered_set<std::string> resultGrants;
|
||||
|
||||
std::set_union(fine_grained_access_handler_.label_permissions().grants().begin(),
|
||||
fine_grained_access_handler_.label_permissions().grants().end(),
|
||||
role_->fine_grained_access_handler().label_permissions().grants().begin(),
|
||||
role_->fine_grained_access_handler().label_permissions().grants().end(),
|
||||
std::inserter(resultGrants, resultGrants.begin()));
|
||||
|
||||
std::unordered_set<std::string> resultDenies;
|
||||
|
||||
std::set_union(fine_grained_access_handler_.label_permissions().denies().begin(),
|
||||
fine_grained_access_handler_.label_permissions().denies().end(),
|
||||
role_->fine_grained_access_handler().label_permissions().denies().begin(),
|
||||
role_->fine_grained_access_handler().label_permissions().denies().end(),
|
||||
std::inserter(resultDenies, resultDenies.begin()));
|
||||
|
||||
return FineGrainedAccessPermissions(resultGrants, resultDenies);
|
||||
}
|
||||
return fine_grained_access_handler_.label_permissions();
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
|
||||
if (role_) {
|
||||
std::unordered_set<std::string> resultGrants;
|
||||
|
||||
std::set_union(fine_grained_access_handler_.edge_type_permissions().grants().begin(),
|
||||
fine_grained_access_handler_.edge_type_permissions().grants().end(),
|
||||
role_->fine_grained_access_handler().edge_type_permissions().grants().begin(),
|
||||
role_->fine_grained_access_handler().edge_type_permissions().grants().end(),
|
||||
std::inserter(resultGrants, resultGrants.begin()));
|
||||
|
||||
std::unordered_set<std::string> resultDenies;
|
||||
|
||||
std::set_union(fine_grained_access_handler_.edge_type_permissions().denies().begin(),
|
||||
fine_grained_access_handler_.edge_type_permissions().denies().end(),
|
||||
role_->fine_grained_access_handler().edge_type_permissions().denies().begin(),
|
||||
role_->fine_grained_access_handler().edge_type_permissions().denies().end(),
|
||||
std::inserter(resultDenies, resultDenies.begin()));
|
||||
|
||||
return FineGrainedAccessPermissions(resultGrants, resultDenies);
|
||||
}
|
||||
return fine_grained_access_handler_.edge_type_permissions();
|
||||
}
|
||||
|
||||
const std::string &User::username() const { return username_; }
|
||||
|
||||
const Permissions &User::permissions() const { return permissions_; }
|
||||
Permissions &User::permissions() { return permissions_; }
|
||||
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
|
||||
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
|
||||
|
||||
const Role *User::role() const {
|
||||
if (role_.has_value()) {
|
||||
@@ -283,6 +493,7 @@ nlohmann::json User::Serialize() const {
|
||||
data["username"] = username_;
|
||||
data["password_hash"] = password_hash_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
// The role shouldn't be serialized here, it is stored as a foreign key.
|
||||
return data;
|
||||
}
|
||||
@@ -291,15 +502,19 @@ User User::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
|
||||
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object() ||
|
||||
!data["fine_grained_access_handler"].is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
return {data["username"], data["password_hash"], permissions};
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
|
||||
}
|
||||
|
||||
bool operator==(const User &first, const User &second) {
|
||||
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
|
||||
first.permissions_ == second.permissions_ && first.role_ == second.role_;
|
||||
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
|
||||
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
|
||||
}
|
||||
|
||||
} // namespace memgraph::auth
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <json/json.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace memgraph::auth {
|
||||
// These permissions must have values that are applicable for usage in a
|
||||
@@ -88,15 +90,74 @@ bool operator==(const Permissions &first, const Permissions &second);
|
||||
|
||||
bool operator!=(const Permissions &first, const Permissions &second);
|
||||
|
||||
class FineGrainedAccessPermissions final {
|
||||
public:
|
||||
explicit FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants = {},
|
||||
const std::unordered_set<std::string> &denies = {});
|
||||
|
||||
PermissionLevel Has(const std::string &permission) const;
|
||||
|
||||
void Grant(const std::string &permission);
|
||||
|
||||
void Revoke(const std::string &permission);
|
||||
|
||||
void Deny(const std::string &permission);
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
|
||||
|
||||
const std::unordered_set<std::string> &grants() const;
|
||||
const std::unordered_set<std::string> &denies() const;
|
||||
|
||||
private:
|
||||
std::unordered_set<std::string> grants_{};
|
||||
std::unordered_set<std::string> denies_{};
|
||||
};
|
||||
|
||||
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
|
||||
|
||||
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
|
||||
|
||||
class FineGrainedAccessHandler final {
|
||||
public:
|
||||
explicit FineGrainedAccessHandler(
|
||||
const FineGrainedAccessPermissions &labelPermissions = FineGrainedAccessPermissions(),
|
||||
const FineGrainedAccessPermissions &edgeTypePermissions = FineGrainedAccessPermissions());
|
||||
|
||||
const FineGrainedAccessPermissions &label_permissions() const;
|
||||
FineGrainedAccessPermissions &label_permissions();
|
||||
|
||||
const FineGrainedAccessPermissions &edge_type_permissions() const;
|
||||
FineGrainedAccessPermissions &edge_type_permissions();
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
static FineGrainedAccessHandler Deserialize(const nlohmann::json &data);
|
||||
|
||||
friend bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
|
||||
|
||||
private:
|
||||
FineGrainedAccessPermissions label_permissions_;
|
||||
FineGrainedAccessPermissions edge_type_permissions_;
|
||||
};
|
||||
|
||||
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
|
||||
|
||||
class Role final {
|
||||
public:
|
||||
Role(const std::string &rolename);
|
||||
|
||||
Role(const std::string &rolename, const Permissions &permissions);
|
||||
Role(const std::string &rolename, const Permissions &permissions,
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler);
|
||||
|
||||
const std::string &rolename() const;
|
||||
const Permissions &permissions() const;
|
||||
Permissions &permissions();
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler() const;
|
||||
FineGrainedAccessHandler &fine_grained_access_handler();
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
@@ -108,6 +169,7 @@ class Role final {
|
||||
private:
|
||||
std::string rolename_;
|
||||
Permissions permissions_;
|
||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||
};
|
||||
|
||||
bool operator==(const Role &first, const Role &second);
|
||||
@@ -117,7 +179,8 @@ class User final {
|
||||
public:
|
||||
User(const std::string &username);
|
||||
|
||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
|
||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler);
|
||||
|
||||
/// @throw AuthException if unable to verify the password.
|
||||
bool CheckPassword(const std::string &password);
|
||||
@@ -130,11 +193,15 @@ class User final {
|
||||
void ClearRole();
|
||||
|
||||
Permissions GetPermissions() const;
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
|
||||
|
||||
const std::string &username() const;
|
||||
|
||||
const Permissions &permissions() const;
|
||||
Permissions &permissions();
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler() const;
|
||||
FineGrainedAccessHandler &fine_grained_access_handler();
|
||||
|
||||
const Role *role() const;
|
||||
|
||||
@@ -149,6 +216,7 @@ class User final {
|
||||
std::string username_;
|
||||
std::string password_hash_;
|
||||
Permissions permissions_;
|
||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||
std::optional<Role> role_;
|
||||
};
|
||||
|
||||
|
||||
@@ -167,7 +167,6 @@ class Value {
|
||||
Value(const Edge &value) : type_(Type::Edge) { new (&edge_v) Edge(value); }
|
||||
Value(const UnboundedEdge &value) : type_(Type::UnboundedEdge) { new (&unbounded_edge_v) UnboundedEdge(value); }
|
||||
Value(const Path &value) : type_(Type::Path) { new (&path_v) Path(value); }
|
||||
|
||||
Value(const utils::Date &date) : type_(Type::Date) { new (&date_v) utils::Date(date); }
|
||||
Value(const utils::LocalTime &time) : type_(Type::LocalTime) { new (&local_time_v) utils::LocalTime(time); }
|
||||
Value(const utils::LocalDateTime &date_time) : type_(Type::LocalDateTime) {
|
||||
|
||||
@@ -127,10 +127,6 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::TypedValue::Type::Duration:
|
||||
return Value(value.ValueDuration());
|
||||
case query::TypedValue::Type::Graph:
|
||||
auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
|
||||
if (maybe_graph.HasError()) return maybe_graph.GetError();
|
||||
return Value(std::move(*maybe_graph));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,30 +183,6 @@ storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, c
|
||||
return communication::bolt::Path(vertices, edges);
|
||||
}
|
||||
|
||||
storage::Result<std::map<std::string, Value>> ToBoltGraph(const query::Graph &graph, const storage::Storage &db,
|
||||
storage::View view) {
|
||||
std::map<std::string, Value> map;
|
||||
std::vector<Value> vertices;
|
||||
vertices.reserve(graph.vertices().size());
|
||||
for (const auto &v : graph.vertices()) {
|
||||
auto maybe_vertex = ToBoltVertex(v, db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
vertices.emplace_back(Value(std::move(*maybe_vertex)));
|
||||
}
|
||||
map.emplace(std::make_pair("nodes", Value(vertices)));
|
||||
|
||||
std::vector<Value> edges;
|
||||
edges.reserve(graph.edges().size());
|
||||
for (const auto &e : graph.edges()) {
|
||||
auto maybe_edge = ToBoltEdge(e, db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
edges.emplace_back(Value(std::move(*maybe_edge)));
|
||||
}
|
||||
map.emplace(std::make_pair("edges", Value(edges)));
|
||||
|
||||
return std::move(map);
|
||||
}
|
||||
|
||||
storage::PropertyValue ToPropertyValue(const Value &value) {
|
||||
switch (value.type()) {
|
||||
case Value::Type::Null:
|
||||
|
||||
@@ -51,15 +51,6 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
|
||||
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
|
||||
storage::View view);
|
||||
|
||||
/// @param query::Graph for converting to communication::bolt::Graph.
|
||||
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(const query::Graph &graph,
|
||||
const storage::Storage &db,
|
||||
storage::View view);
|
||||
|
||||
/// @param query::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::View for ToBoltVertex and ToBoltEdge.
|
||||
|
||||
@@ -506,7 +506,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
|
||||
if (first_user) {
|
||||
spdlog::info("{} is first created user. Granting all privileges.", username);
|
||||
GrantPrivilege(username, memgraph::query::kPrivilegesAll);
|
||||
GrantPrivilege(username, memgraph::query::kPrivilegesAll, {"*"}, {"*"});
|
||||
}
|
||||
|
||||
return user_added;
|
||||
@@ -751,9 +751,28 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
}
|
||||
}
|
||||
|
||||
memgraph::auth::User *GetUser(const std::string &username) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
|
||||
return new memgraph::auth::User(*user);
|
||||
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void GrantPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
|
||||
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
|
||||
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
@@ -762,8 +781,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
}
|
||||
|
||||
void DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
|
||||
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
|
||||
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
@@ -772,8 +792,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
}
|
||||
|
||||
void RevokePrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
|
||||
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
|
||||
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
@@ -784,7 +805,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
private:
|
||||
template <class TEditFun>
|
||||
void EditPermissions(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, const TEditFun &edit_fun) {
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes,
|
||||
const TEditFun &edit_fun) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
@@ -804,11 +827,25 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_fun(&user->permissions(), permission);
|
||||
}
|
||||
for (const auto &label : labels) {
|
||||
edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
|
||||
}
|
||||
for (const auto &edgeType : edgeTypes) {
|
||||
edit_fun(&user->fine_grained_access_handler().edge_type_permissions(), edgeType);
|
||||
}
|
||||
|
||||
locked_auth->SaveUser(*user);
|
||||
} else {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_fun(&role->permissions(), permission);
|
||||
}
|
||||
for (const auto &label : labels) {
|
||||
edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
|
||||
}
|
||||
for (const auto &edgeType : edgeTypes) {
|
||||
edit_fun(&role->fine_grained_access_handler().edge_type_permissions(), edgeType);
|
||||
}
|
||||
|
||||
locked_auth->SaveRole(*role);
|
||||
}
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
@@ -848,6 +885,20 @@ class AuthChecker final : public memgraph::query::AuthChecker {
|
||||
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const std::vector<memgraph::storage::LabelId> &labels) const final {
|
||||
return std::any_of(labels.begin(), labels.end(), [dba, user](const auto label) {
|
||||
return user->GetFineGrainedAccessLabelPermissions().Has(dba->LabelToName(label)) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
});
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedEdgeType(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const memgraph::storage::EdgeTypeId &edgeType) const final {
|
||||
return user->GetFineGrainedAccessEdgeTypePermissions().Has(dba->EdgeTypeToName(edgeType)) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
@@ -1252,9 +1303,8 @@ int main(int argc, char **argv) {
|
||||
// the triggers
|
||||
auto storage_accessor = interpreter_context.db->Access();
|
||||
auto dba = memgraph::query::DbAccessor{&storage_accessor};
|
||||
interpreter_context.trigger_store.RestoreTriggers(&interpreter_context.ast_cache, &dba,
|
||||
&interpreter_context.antlr_lock, interpreter_context.config.query,
|
||||
interpreter_context.auth_checker);
|
||||
interpreter_context.trigger_store.RestoreTriggers(
|
||||
&interpreter_context.ast_cache, &dba, interpreter_context.config.query, interpreter_context.auth_checker);
|
||||
}
|
||||
|
||||
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
|
||||
|
||||
@@ -39,9 +39,7 @@ set(mg_query_sources
|
||||
stream/common.cpp
|
||||
trigger.cpp
|
||||
trigger_context.cpp
|
||||
typed_value.cpp
|
||||
graph.cpp
|
||||
db_accessor.cpp)
|
||||
typed_value.cpp)
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
@@ -84,7 +82,7 @@ add_custom_command(
|
||||
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.9.2-complete.jar
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
||||
-Dlanguage=Cpp -visitor -package antlropencypher
|
||||
-o ${opencypher_generated}
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
|
||||
@@ -11,13 +11,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "auth/models.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
class AuthChecker {
|
||||
public:
|
||||
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
|
||||
|
||||
virtual bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const std::vector<memgraph::storage::LabelId> &labels) const = 0;
|
||||
|
||||
virtual bool IsUserAuthorizedEdgeType(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const memgraph::storage::EdgeTypeId &edgeType) const = 0;
|
||||
};
|
||||
|
||||
class AllowEverythingAuthChecker final : public query::AuthChecker {
|
||||
@@ -25,5 +31,14 @@ class AllowEverythingAuthChecker final : public query::AuthChecker {
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
|
||||
return true;
|
||||
}
|
||||
bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const std::vector<memgraph::storage::LabelId> &labels) const override {
|
||||
return true;
|
||||
};
|
||||
|
||||
bool IsUserAuthorizedEdgeType(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const memgraph::storage::EdgeTypeId &edgeType) const override {
|
||||
return true;
|
||||
};
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -61,7 +61,6 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
case TypedValue::Type::Graph:
|
||||
throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type());
|
||||
case TypedValue::Type::Null:
|
||||
LOG_FATAL("Invalid type");
|
||||
|
||||
@@ -72,6 +72,8 @@ struct ExecutionContext {
|
||||
ExecutionStats execution_stats;
|
||||
TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
AuthChecker *auth_checker{nullptr};
|
||||
memgraph::auth::User *user{nullptr};
|
||||
};
|
||||
|
||||
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace memgraph::query {
|
||||
CachedPlan::CachedPlan(std::unique_ptr<LogicalPlan> plan) : plan_(std::move(plan)) {}
|
||||
|
||||
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> ¶ms,
|
||||
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
|
||||
const InterpreterConfig::Query &query_config) {
|
||||
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config) {
|
||||
// Strip the query for caching purposes. The process of stripping a query
|
||||
// "normalizes" it by replacing any literals with new parameters. This
|
||||
// results in just the *structure* of the query being taken into account for
|
||||
@@ -63,20 +62,16 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
};
|
||||
|
||||
if (it == accessor.end()) {
|
||||
{
|
||||
std::unique_lock<utils::SpinLock> guard(*antlr_lock);
|
||||
try {
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(stripped_query.query());
|
||||
} catch (const SyntaxException &e) {
|
||||
// There is a syntax exception in the stripped query. Re-run the parser
|
||||
// on the original query to get an appropriate error messsage.
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(query_string);
|
||||
|
||||
try {
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(stripped_query.query());
|
||||
} catch (const SyntaxException &e) {
|
||||
// There is a syntax exception in the stripped query. Re-run the parser
|
||||
// on the original query to get an appropriate error messsage.
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(query_string);
|
||||
|
||||
// If an exception was not thrown here, the stripper messed something
|
||||
// up.
|
||||
LOG_FATAL("The stripped query can't be parsed, but the original can.");
|
||||
}
|
||||
// If an exception was not thrown here, the stripper messed something
|
||||
// up.
|
||||
LOG_FATAL("The stripped query can't be parsed, but the original can.");
|
||||
}
|
||||
|
||||
// Convert the ANTLR4 parse tree into an AST.
|
||||
|
||||
@@ -111,8 +111,7 @@ struct ParsedQuery {
|
||||
};
|
||||
|
||||
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> ¶ms,
|
||||
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
|
||||
const InterpreterConfig::Query &query_config);
|
||||
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config);
|
||||
|
||||
class SingleNodeLogicalPlan final : public LogicalPlan {
|
||||
public:
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright 2022 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/db_accessor.hpp"
|
||||
|
||||
#include "query/graph.hpp"
|
||||
|
||||
#include <cppitertools/filter.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
SubgraphDbAccessor::SubgraphDbAccessor(query::DbAccessor *db_accessor, Graph *graph)
|
||||
: db_accessor_(db_accessor), graph_(graph) {}
|
||||
|
||||
SubgraphDbAccessor *SubgraphDbAccessor::MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph) {
|
||||
return new SubgraphDbAccessor(db_accessor, graph);
|
||||
}
|
||||
|
||||
storage::PropertyId SubgraphDbAccessor::NameToProperty(const std::string_view name) {
|
||||
return db_accessor_->NameToProperty(name);
|
||||
}
|
||||
|
||||
storage::LabelId SubgraphDbAccessor::NameToLabel(const std::string_view name) {
|
||||
return db_accessor_->NameToLabel(name);
|
||||
}
|
||||
|
||||
storage::EdgeTypeId SubgraphDbAccessor::NameToEdgeType(const std::string_view name) {
|
||||
return db_accessor_->NameToEdgeType(name);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::PropertyToName(storage::PropertyId prop) const {
|
||||
return db_accessor_->PropertyToName(prop);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::LabelToName(storage::LabelId label) const {
|
||||
return db_accessor_->LabelToName(label);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::EdgeTypeToName(storage::EdgeTypeId type) const {
|
||||
return db_accessor_->EdgeTypeToName(type);
|
||||
}
|
||||
|
||||
storage::Result<std::optional<EdgeAccessor>> SubgraphDbAccessor::RemoveEdge(EdgeAccessor *edge) {
|
||||
auto result = db_accessor_->RemoveEdge(edge);
|
||||
if (result.HasError() || !*result) {
|
||||
return result;
|
||||
}
|
||||
return this->graph_->RemoveEdge(*edge);
|
||||
}
|
||||
|
||||
storage::Result<EdgeAccessor> SubgraphDbAccessor::InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
|
||||
const storage::EdgeTypeId &edge_type) {
|
||||
VertexAccessor *from_impl = &from->impl_;
|
||||
VertexAccessor *to_impl = &to->impl_;
|
||||
|
||||
auto result = db_accessor_->InsertEdge(from_impl, to_impl, edge_type);
|
||||
if (result.HasError()) {
|
||||
return result;
|
||||
}
|
||||
this->graph_->InsertEdge(*result);
|
||||
return result;
|
||||
}
|
||||
|
||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
|
||||
SubgraphDbAccessor::DetachRemoveVertex(
|
||||
VertexAccessor *) { // NOLINT(hicpp-named-parameter, readability-convert-member-functions-to-static)
|
||||
throw std::logic_error{"Such operation not possible on subgraph"};
|
||||
}
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
|
||||
SubgraphVertexAccessor *subgraphvertex_accessor) {
|
||||
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
|
||||
auto result = db_accessor_->RemoveVertex(vertex_accessor);
|
||||
if (result.HasError() || !*result) {
|
||||
return result;
|
||||
}
|
||||
return this->graph_->RemoveVertex(*vertex_accessor);
|
||||
}
|
||||
|
||||
SubgraphVertexAccessor SubgraphDbAccessor::InsertVertex() {
|
||||
VertexAccessor vertex = db_accessor_->InsertVertex();
|
||||
this->graph_->InsertVertex(vertex);
|
||||
return SubgraphVertexAccessor(vertex, this->getGraph());
|
||||
}
|
||||
|
||||
VerticesIterable SubgraphDbAccessor::Vertices(storage::View) {
|
||||
return VerticesIterable(graph_->vertices());
|
||||
} // NOLINT(hicpp-named-parameter)
|
||||
|
||||
std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, storage::View view) {
|
||||
std::optional<VertexAccessor> maybe_vertex = db_accessor_->FindVertex(gid, view);
|
||||
if (maybe_vertex && this->graph_->ContainsVertex(*maybe_vertex)) {
|
||||
return *maybe_vertex;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
query::Graph *SubgraphDbAccessor::getGraph() { return graph_; }
|
||||
|
||||
VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; }
|
||||
|
||||
auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)) {
|
||||
auto maybe_edges = impl_.impl_.OutEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
auto graph_edges = graph_->edges();
|
||||
|
||||
std::unordered_set<storage::EdgeAccessor> graph_edges_storage;
|
||||
|
||||
for (auto e : graph_edges) {
|
||||
graph_edges_storage.insert(e.impl_);
|
||||
}
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
if (std::find(begin(graph_edges_storage), end(graph_edges_storage), edge) != std::end(graph_edges_storage)) {
|
||||
filteredOutEdges.push_back(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
|
||||
}
|
||||
|
||||
auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_.InEdges(view)) {
|
||||
auto maybe_edges = impl_.impl_.InEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
auto graph_edges = graph_->edges();
|
||||
|
||||
std::unordered_set<storage::EdgeAccessor> graph_edges_storage;
|
||||
|
||||
for (auto e : graph_edges) {
|
||||
graph_edges_storage.insert(e.impl_);
|
||||
}
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
if (std::find(begin(graph_edges_storage), end(graph_edges_storage), edge) != std::end(graph_edges_storage)) {
|
||||
filteredOutEdges.push_back(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
|
||||
}
|
||||
|
||||
} // namespace memgraph::query
|
||||
@@ -20,8 +20,6 @@
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Our communication layer and query engine don't mix
|
||||
@@ -47,7 +45,6 @@
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class Graph;
|
||||
class VertexAccessor;
|
||||
|
||||
class EdgeAccessor final {
|
||||
@@ -188,115 +185,38 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F
|
||||
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class SubgraphVertexAccessor final {
|
||||
public:
|
||||
query::VertexAccessor impl_;
|
||||
query::Graph *graph_;
|
||||
|
||||
explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {}
|
||||
|
||||
bool operator==(const SubgraphVertexAccessor &v) const noexcept {
|
||||
static_assert(noexcept(impl_ == v.impl_));
|
||||
return impl_ == v.impl_;
|
||||
}
|
||||
|
||||
auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto Labels(storage::View view) const { return impl_.Labels(view); }
|
||||
|
||||
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
|
||||
|
||||
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
|
||||
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
|
||||
return impl_.HasLabel(view, label);
|
||||
}
|
||||
|
||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
||||
|
||||
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
||||
return impl_.GetProperty(view, key);
|
||||
}
|
||||
|
||||
storage::Gid Gid() const noexcept { return impl_.Gid(); }
|
||||
|
||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
VertexAccessor GetVertexAccessor() const;
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::VertexAccessor> {
|
||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::EdgeAccessor> {
|
||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class VerticesIterable final {
|
||||
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
|
||||
std::equal_to<void>, utils::Allocator<VertexAccessor>>>
|
||||
iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
std::variant<storage::VerticesIterable::Iterator,
|
||||
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator>
|
||||
it_;
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator it)
|
||||
: it_(it) {}
|
||||
|
||||
VertexAccessor operator*() const {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto it_) { return VertexAccessor(*it_); }}, it_);
|
||||
}
|
||||
|
||||
Iterator &operator++() {
|
||||
std::visit(memgraph::utils::Overloaded{[this](auto it_) { this->it_ = ++it_; }}, it_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>
|
||||
vertices)
|
||||
: iterable_(vertices) {}
|
||||
|
||||
Iterator begin() {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto &iterable_) { return Iterator(iterable_.begin()); }},
|
||||
iterable_);
|
||||
}
|
||||
|
||||
Iterator end() {
|
||||
return std::visit(memgraph::utils::Overloaded{[](auto &iterable_) { return Iterator(iterable_.end()); }},
|
||||
iterable_);
|
||||
}
|
||||
};
|
||||
|
||||
class DbAccessor final {
|
||||
storage::Storage::Accessor *accessor_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::VerticesIterable iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::VerticesIterable::Iterator it_;
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
|
||||
Iterator begin() { return Iterator(iterable_.begin()); }
|
||||
|
||||
Iterator end() { return Iterator(iterable_.end()); }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
@@ -438,44 +358,18 @@ class DbAccessor final {
|
||||
storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
|
||||
};
|
||||
|
||||
class SubgraphDbAccessor final {
|
||||
DbAccessor *db_accessor_;
|
||||
Graph *graph_;
|
||||
} // namespace memgraph::query
|
||||
|
||||
public:
|
||||
explicit SubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph);
|
||||
namespace std {
|
||||
|
||||
static SubgraphDbAccessor *MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph);
|
||||
|
||||
storage::PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
storage::LabelId NameToLabel(std::string_view name);
|
||||
|
||||
storage::EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
const std::string &PropertyToName(storage::PropertyId prop) const;
|
||||
|
||||
const std::string &LabelToName(storage::LabelId label) const;
|
||||
|
||||
const std::string &EdgeTypeToName(storage::EdgeTypeId type) const;
|
||||
|
||||
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge);
|
||||
|
||||
storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
|
||||
const storage::EdgeTypeId &edge_type);
|
||||
|
||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||
VertexAccessor *vertex_accessor);
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> RemoveVertex(SubgraphVertexAccessor *vertex_accessor);
|
||||
|
||||
SubgraphVertexAccessor InsertVertex();
|
||||
|
||||
VerticesIterable Vertices(storage::View view);
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view);
|
||||
|
||||
Graph *getGraph();
|
||||
template <>
|
||||
struct hash<memgraph::query::VertexAccessor> {
|
||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::query
|
||||
template <>
|
||||
struct hash<memgraph::query::EdgeAccessor> {
|
||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -464,7 +464,7 @@ cpp<#
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to."))
|
||||
(:public
|
||||
(lcp:define-enum op
|
||||
(count min max sum avg collect-list collect-map project)
|
||||
(count min max sum avg collect-list collect-map)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
Aggregation() = default;
|
||||
@@ -475,11 +475,10 @@ cpp<#
|
||||
static const constexpr char *const kSum = "SUM";
|
||||
static const constexpr char *const kAvg = "AVG";
|
||||
static const constexpr char *const kCollect = "COLLECT";
|
||||
static const constexpr char *const kProject = "PROJECT";
|
||||
|
||||
static std::string OpToString(Op op) {
|
||||
const char *op_strings[] = {kCount, kMin, kMax, kSum,
|
||||
kAvg, kCollect, kCollect, kProject};
|
||||
kAvg, kCollect, kCollect};
|
||||
return op_strings[static_cast<int>(op)];
|
||||
}
|
||||
|
||||
@@ -2235,6 +2234,7 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
|
||||
(lcp:define-class auth-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(user "std::string" :scope :public)
|
||||
@@ -2243,7 +2243,9 @@ cpp<#
|
||||
(password "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(privileges "std::vector<Privilege>" :scope :public))
|
||||
(privileges "std::vector<Privilege>" :scope :public)
|
||||
(labels "std::vector<std::string>" :scope :public)
|
||||
(edgeTypes "std::vector<std::string>" :scope :public))
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(create-role drop-role show-roles create-user set-password drop-user
|
||||
@@ -2265,13 +2267,16 @@ cpp<#
|
||||
#>cpp
|
||||
AuthQuery(Action action, std::string user, std::string role,
|
||||
std::string user_or_role, Expression *password,
|
||||
std::vector<Privilege> privileges)
|
||||
std::vector<Privilege> privileges, std::vector<std::string> labels,
|
||||
std::vector<std::string> edgeTypes)
|
||||
: action_(action),
|
||||
user_(user),
|
||||
role_(role),
|
||||
user_or_role_(user_or_role),
|
||||
password_(password),
|
||||
privileges_(privileges) {}
|
||||
privileges_(privileges),
|
||||
labels_(labels),
|
||||
edgetypes_(edgeTypes) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -115,7 +115,7 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
auto operators = ExtractOperators(all_children, allowed_operators);
|
||||
|
||||
for (auto *expression : _expressions) {
|
||||
expressions.push_back(expression->accept(this));
|
||||
expressions.push_back(std::any_cast<Expression *>(expression->accept(this)));
|
||||
}
|
||||
|
||||
Expression *first_operand = expressions[0];
|
||||
@@ -131,7 +131,7 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
DMG_ASSERT(_expression, "can't happen");
|
||||
auto operators = ExtractOperators(all_children, allowed_operators);
|
||||
|
||||
Expression *expression = _expression->accept(this);
|
||||
Expression *expression = std::any_cast<Expression *>(_expression->accept(this));
|
||||
for (int i = (int)operators.size() - 1; i >= 0; --i) {
|
||||
expression = CreateUnaryOperatorByToken(operators[i], expression);
|
||||
}
|
||||
@@ -468,6 +468,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitRevokePrivilege(MemgraphCypher::RevokePrivilegeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitEdgeTypeList(MemgraphCypher::EdgeTypeListContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery::Privilege
|
||||
*/
|
||||
@@ -478,6 +483,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery::LabelList
|
||||
*/
|
||||
antlrcpp::Any visitLabelList(MemgraphCypher::LabelListContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| DENY
|
||||
| DROP
|
||||
| DUMP
|
||||
| EDGE_TYPES
|
||||
| EXECUTE
|
||||
| FOR
|
||||
| FOREACH
|
||||
@@ -56,6 +57,7 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| IDENTIFIED
|
||||
| ISOLATION
|
||||
| KAFKA
|
||||
| LABELS
|
||||
| LEVEL
|
||||
| LOAD
|
||||
| LOCK
|
||||
@@ -254,10 +256,24 @@ privilege : CREATE
|
||||
| MODULE_READ
|
||||
| MODULE_WRITE
|
||||
| WEBSOCKET
|
||||
| EDGE_TYPES edgeTypes = edgeTypeList
|
||||
| LABELS labels=labelList
|
||||
;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
|
||||
edgeTypeList : '*' | listOfEdgeTypes ;
|
||||
|
||||
listOfEdgeTypes : edgeType ( ',' edgeType )* ;
|
||||
|
||||
edgeType : COLON symbolicName ;
|
||||
|
||||
labelList : '*' | listOfLabels ;
|
||||
|
||||
listOfLabels : label ( ',' label )* ;
|
||||
|
||||
label : COLON symbolicName ;
|
||||
|
||||
showPrivileges : SHOW PRIVILEGES FOR userOrRole=userOrRoleName ;
|
||||
|
||||
showRoleForUser : SHOW ROLE FOR user=userOrRoleName ;
|
||||
|
||||
@@ -66,6 +66,7 @@ IDENTIFIED : I D E N T I F I E D ;
|
||||
IGNORE : I G N O R E ;
|
||||
ISOLATION : I S O L A T I O N ;
|
||||
KAFKA : K A F K A ;
|
||||
LABELS : L A B E L S ;
|
||||
LEVEL : L E V E L ;
|
||||
LOAD : L O A D ;
|
||||
LOCK : L O C K ;
|
||||
@@ -114,3 +115,4 @@ USER : U S E R ;
|
||||
USERS : U S E R S ;
|
||||
VERSION : V E R S I O N ;
|
||||
WEBSOCKET : W E B S O C K E T ;
|
||||
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;
|
||||
|
||||
@@ -204,8 +204,10 @@ const trie::Trie kKeywords = {"union",
|
||||
"pulsar",
|
||||
"service_url",
|
||||
"version",
|
||||
"websocket"
|
||||
"foreach"};
|
||||
"websocket",
|
||||
"foreach",
|
||||
"labels",
|
||||
"edge_types"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2022 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/graph.hpp"
|
||||
#include "query/path.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
Graph::Graph(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {}
|
||||
|
||||
Graph::Graph(const Graph &other, utils::MemoryResource *memory)
|
||||
: vertices_(other.vertices_, memory), edges_(other.edges_, memory) {}
|
||||
|
||||
Graph::Graph(Graph &&other) noexcept : Graph(std::move(other), other.GetMemoryResource()) {}
|
||||
|
||||
Graph::Graph(const Graph &other)
|
||||
: Graph(other,
|
||||
std::allocator_traits<allocator_type>::select_on_container_copy_construction(other.GetMemoryResource())
|
||||
.GetMemoryResource()) {}
|
||||
|
||||
Graph::Graph(Graph &&other, utils::MemoryResource *memory)
|
||||
: vertices_(std::move(other.vertices_), memory), edges_(std::move(other.edges_), memory) {}
|
||||
|
||||
void Graph::Expand(const Path &path) {
|
||||
const auto &path_vertices_ = path.vertices();
|
||||
const auto &path_edges_ = path.edges();
|
||||
std::for_each(path_vertices_.begin(), path_vertices_.end(), [this](const VertexAccessor v) { vertices_.insert(v); });
|
||||
std::for_each(path_edges_.begin(), path_edges_.end(), [this](const EdgeAccessor e) { edges_.insert(e); });
|
||||
}
|
||||
|
||||
void Graph::InsertVertex(const VertexAccessor &vertex) { vertices_.insert(vertex); }
|
||||
|
||||
void Graph::InsertEdge(const EdgeAccessor &edge) { edges_.insert(edge); }
|
||||
|
||||
bool Graph::ContainsVertex(const VertexAccessor &vertex) {
|
||||
return std::find(begin(vertices_), end(vertices_), vertex) != std::end(vertices_);
|
||||
}
|
||||
|
||||
std::optional<VertexAccessor> Graph::RemoveVertex(const VertexAccessor &vertex) {
|
||||
if (!ContainsVertex(vertex)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto value = vertices_.erase(vertex);
|
||||
if (value == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return vertex;
|
||||
}
|
||||
|
||||
std::optional<EdgeAccessor> Graph::RemoveEdge(const EdgeAccessor &edge) {
|
||||
auto value = edges_.erase(edge);
|
||||
if (value == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return edge;
|
||||
}
|
||||
|
||||
std::vector<EdgeAccessor> Graph::OutEdges(VertexAccessor vertex_accessor) {
|
||||
std::vector<EdgeAccessor> out_edges;
|
||||
for (const auto &edge : edges_) {
|
||||
if (edge.From() == vertex_accessor) {
|
||||
out_edges.emplace_back(edge);
|
||||
}
|
||||
}
|
||||
return out_edges;
|
||||
}
|
||||
|
||||
/** Copy assign other, utils::MemoryResource of `this` is used */
|
||||
Graph &Graph::operator=(const Graph &) = default;
|
||||
|
||||
/** Move assign other, utils::MemoryResource of `this` is used. */
|
||||
Graph &Graph::operator=(Graph &&) noexcept = default;
|
||||
|
||||
Graph::~Graph() = default;
|
||||
|
||||
utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() { return vertices_; }
|
||||
utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() { return edges_; }
|
||||
const utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() const { return vertices_; }
|
||||
const utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() const { return edges_; }
|
||||
|
||||
utils::MemoryResource *Graph::GetMemoryResource() const { return vertices_.get_allocator().GetMemoryResource(); }
|
||||
|
||||
} // namespace memgraph::query
|
||||
@@ -1,111 +0,0 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class Path;
|
||||
/**
|
||||
* A data structure that holds a graph. A graph consists of at least one
|
||||
* vertex, and zero or more edges.
|
||||
*/
|
||||
class Graph final {
|
||||
public:
|
||||
/** Allocator type so that STL containers are aware that we need one */
|
||||
using allocator_type = utils::Allocator<char>;
|
||||
|
||||
/**
|
||||
* Create the graph with no elements
|
||||
* Allocations are done using the given MemoryResource.
|
||||
*/
|
||||
explicit Graph(utils::MemoryResource *memory);
|
||||
|
||||
/**
|
||||
* Construct a copy of other.
|
||||
* utils::MemoryResource is obtained by calling
|
||||
* std::allocator_traits<>::
|
||||
* select_on_container_copy_construction(other.GetMemoryResource()).
|
||||
* Since we use utils::Allocator, which does not propagate, this means that we
|
||||
* will default to utils::NewDeleteResource().
|
||||
*/
|
||||
Graph(const Graph &other);
|
||||
|
||||
/** Construct a copy using the given utils::MemoryResource */
|
||||
Graph(const Graph &other, utils::MemoryResource *memory);
|
||||
|
||||
/**
|
||||
* Construct with the value of other.
|
||||
* utils::MemoryResource is obtained from other. After the move, other will be
|
||||
* empty.
|
||||
*/
|
||||
Graph(Graph &&other) noexcept;
|
||||
|
||||
/**
|
||||
* Construct with the value of other, but use the given utils::MemoryResource.
|
||||
* After the move, other may not be empty if `*memory !=
|
||||
* *other.GetMemoryResource()`, because an element-wise move will be
|
||||
* performed.
|
||||
*/
|
||||
Graph(Graph &&other, utils::MemoryResource *memory);
|
||||
|
||||
/** Expands the graph with the given path. */
|
||||
void Expand(const Path &path);
|
||||
|
||||
/** Inserts the vertex in the graph. */
|
||||
void InsertVertex(const VertexAccessor &vertex);
|
||||
|
||||
/** Inserts the edge in the graph. */
|
||||
void InsertEdge(const EdgeAccessor &edge);
|
||||
|
||||
/** Checks whether the graph contains the vertex. */
|
||||
bool ContainsVertex(const VertexAccessor &vertex);
|
||||
|
||||
/** Removes the vertex from the graph if the vertex is in the graph. */
|
||||
std::optional<VertexAccessor> RemoveVertex(const VertexAccessor &vertex);
|
||||
|
||||
/** Removes the vertex from the graph if the vertex is in the graph. */
|
||||
std::optional<EdgeAccessor> RemoveEdge(const EdgeAccessor &edge);
|
||||
|
||||
/** Return the out edges of the given vertex. */
|
||||
std::vector<EdgeAccessor> OutEdges(VertexAccessor vertex_accessor);
|
||||
|
||||
/** Copy assign other, utils::MemoryResource of `this` is used */
|
||||
Graph &operator=(const Graph &);
|
||||
|
||||
/** Move assign other, utils::MemoryResource of `this` is used. */
|
||||
Graph &operator=(Graph &&) noexcept;
|
||||
|
||||
~Graph();
|
||||
|
||||
utils::pmr::unordered_set<VertexAccessor> &vertices();
|
||||
utils::pmr::unordered_set<EdgeAccessor> &edges();
|
||||
const utils::pmr::unordered_set<VertexAccessor> &vertices() const;
|
||||
const utils::pmr::unordered_set<EdgeAccessor> &edges() const;
|
||||
|
||||
utils::MemoryResource *GetMemoryResource() const;
|
||||
|
||||
private:
|
||||
// Contains all the vertices in the Graph.
|
||||
utils::pmr::unordered_set<VertexAccessor> vertices_;
|
||||
// Contains all the edges in the Graph
|
||||
utils::pmr::unordered_set<EdgeAccessor> edges_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query
|
||||
@@ -587,8 +587,6 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
|
||||
return TypedValue("LOCAL_DATE_TIME", ctx.memory);
|
||||
case TypedValue::Type::Duration:
|
||||
return TypedValue("DURATION", ctx.memory);
|
||||
case TypedValue::Type::Graph:
|
||||
throw QueryRuntimeException("Trying to get graph");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -365,9 +365,6 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
||||
}
|
||||
case TypedValue::Type::Graph: {
|
||||
throw QueryRuntimeException("Invalid operation for Graph");
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
#include "query/stream/common.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/csv_parsing.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
@@ -273,6 +274,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
|
||||
|
||||
@@ -280,6 +282,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
std::string rolename = auth_query->role_;
|
||||
std::string user_or_role = auth_query->user_or_role_;
|
||||
std::vector<AuthQuery::Privilege> privileges = auth_query->privileges_;
|
||||
std::vector<std::string> edgeTypes = auth_query->edgetypes_;
|
||||
std::vector<std::string> labels = auth_query->labels_;
|
||||
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
|
||||
|
||||
Callback callback;
|
||||
@@ -292,10 +296,11 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
AuthQuery::Action::REVOKE_PRIVILEGE, AuthQuery::Action::SHOW_PRIVILEGES, AuthQuery::Action::SHOW_USERS_FOR_ROLE,
|
||||
AuthQuery::Action::SHOW_ROLE_FOR_USER};
|
||||
|
||||
if (license_check_result.HasError() && enterprise_only_methods.contains(auth_query->action_)) {
|
||||
throw utils::BasicException(
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication features"));
|
||||
}
|
||||
// if (license_check_result.HasError() && enterprise_only_methods.contains(auth_query->action_)) {
|
||||
// throw utils::BasicException(
|
||||
// utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication
|
||||
// features"));
|
||||
// }
|
||||
|
||||
switch (auth_query->action_) {
|
||||
case AuthQuery::Action::CREATE_USER:
|
||||
@@ -309,7 +314,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
// If the license is not valid we create users with admin access
|
||||
if (!valid_enterprise_license) {
|
||||
spdlog::warn("Granting all the privileges to {}.", username);
|
||||
auth->GrantPrivilege(username, kPrivilegesAll);
|
||||
auth->GrantPrivilege(username, kPrivilegesAll, {"*"}, {"*"});
|
||||
}
|
||||
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
@@ -384,20 +389,20 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::GRANT_PRIVILEGE:
|
||||
callback.fn = [auth, user_or_role, privileges] {
|
||||
auth->GrantPrivilege(user_or_role, privileges);
|
||||
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
|
||||
auth->GrantPrivilege(user_or_role, privileges, labels, edgeTypes);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::DENY_PRIVILEGE:
|
||||
callback.fn = [auth, user_or_role, privileges] {
|
||||
auth->DenyPrivilege(user_or_role, privileges);
|
||||
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
|
||||
auth->DenyPrivilege(user_or_role, privileges, labels, edgeTypes);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
case AuthQuery::Action::REVOKE_PRIVILEGE: {
|
||||
callback.fn = [auth, user_or_role, privileges] {
|
||||
auth->RevokePrivilege(user_or_role, privileges);
|
||||
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
|
||||
auth->RevokePrivilege(user_or_role, privileges, labels, edgeTypes);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
@@ -897,7 +902,7 @@ struct PullPlanVector {
|
||||
struct PullPlan {
|
||||
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, bool is_profile_query,
|
||||
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
|
||||
TriggerContextCollector *trigger_context_collector = nullptr,
|
||||
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector = nullptr,
|
||||
std::optional<size_t> memory_limit = {});
|
||||
std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n,
|
||||
const std::vector<Symbol> &output_symbols,
|
||||
@@ -926,7 +931,8 @@ struct PullPlan {
|
||||
|
||||
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, const bool is_profile_query,
|
||||
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
|
||||
TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit)
|
||||
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector,
|
||||
const std::optional<size_t> memory_limit)
|
||||
: plan_(plan),
|
||||
cursor_(plan->plan().MakeCursor(execution_memory)),
|
||||
frame_(plan->symbol_table().max_position(), execution_memory),
|
||||
@@ -937,6 +943,12 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par
|
||||
ctx_.evaluation_context.parameters = parameters;
|
||||
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, dba);
|
||||
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (username.has_value()) {
|
||||
ctx_.user = interpreter_context->auth->GetUser(*username);
|
||||
ctx_.auth_checker = interpreter_context->auth_checker;
|
||||
}
|
||||
#endif
|
||||
if (interpreter_context->config.execution_timeout_sec > 0) {
|
||||
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
|
||||
}
|
||||
@@ -1110,6 +1122,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
|
||||
const std::string *username,
|
||||
TriggerContextCollector *trigger_context_collector = nullptr) {
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
|
||||
|
||||
@@ -1118,6 +1131,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
EvaluationContext evaluation_context;
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
evaluation_context.parameters = parsed_query.parameters;
|
||||
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::View::OLD);
|
||||
const auto memory_limit = EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
|
||||
if (memory_limit) {
|
||||
@@ -1153,8 +1167,9 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
header.push_back(
|
||||
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
|
||||
}
|
||||
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
|
||||
execution_memory, trigger_context_collector, memory_limit);
|
||||
auto pull_plan =
|
||||
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory,
|
||||
StringPointerToOptional(username), trigger_context_collector, memory_limit);
|
||||
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
|
||||
[pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary](
|
||||
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
@@ -1181,7 +1196,7 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
||||
// full query string) when given just the inner query to execute.
|
||||
ParsedQuery parsed_inner_query =
|
||||
ParseQuery(parsed_query.query_string.substr(kExplainQueryStart.size()), parsed_query.user_parameters,
|
||||
&interpreter_context->ast_cache, &interpreter_context->antlr_lock, interpreter_context->config.query);
|
||||
&interpreter_context->ast_cache, interpreter_context->config.query);
|
||||
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_inner_query.query);
|
||||
MG_ASSERT(cypher_query, "Cypher grammar should not allow other queries in EXPLAIN");
|
||||
@@ -1214,7 +1229,8 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
||||
|
||||
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory) {
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory,
|
||||
const std::string *username) {
|
||||
const std::string kProfileQueryStart = "profile ";
|
||||
|
||||
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
|
||||
@@ -1248,7 +1264,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
// full query string) when given just the inner query to execute.
|
||||
ParsedQuery parsed_inner_query =
|
||||
ParseQuery(parsed_query.query_string.substr(kProfileQueryStart.size()), parsed_query.user_parameters,
|
||||
&interpreter_context->ast_cache, &interpreter_context->antlr_lock, interpreter_context->config.query);
|
||||
&interpreter_context->ast_cache, interpreter_context->config.query);
|
||||
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_inner_query.query);
|
||||
MG_ASSERT(cypher_query, "Cypher grammar should not allow other queries in PROFILE");
|
||||
@@ -1264,12 +1280,14 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
|
||||
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
|
||||
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
||||
auto optional_username = StringPointerToOptional(username);
|
||||
|
||||
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
|
||||
|
||||
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[plan = std::move(cypher_query_plan), parameters = std::move(parsed_inner_query.parameters),
|
||||
summary, dba, interpreter_context, execution_memory, memory_limit,
|
||||
summary, dba, interpreter_context, execution_memory, memory_limit, optional_username,
|
||||
// We want to execute the query we are profiling lazily, so we delay
|
||||
// the construction of the corresponding context.
|
||||
stats_and_total_time = std::optional<plan::ProfilingStatsWithTotalTime>{},
|
||||
@@ -1278,7 +1296,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
// No output symbols are given so that nothing is streamed.
|
||||
if (!stats_and_total_time) {
|
||||
stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context,
|
||||
execution_memory, nullptr, memory_limit)
|
||||
execution_memory, optional_username, nullptr, memory_limit)
|
||||
.Pull(stream, {}, {}, summary);
|
||||
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
|
||||
}
|
||||
@@ -1413,7 +1431,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
|
||||
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory) {
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory, const std::string *username) {
|
||||
if (in_explicit_transaction) {
|
||||
throw UserModificationInMulticommandTxException();
|
||||
}
|
||||
@@ -1433,8 +1451,8 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
[fn = callback.fn](Frame *, ExecutionContext *) { return fn(); }),
|
||||
0.0, AstStorage{}, symbol_table));
|
||||
|
||||
auto pull_plan =
|
||||
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory);
|
||||
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
|
||||
execution_memory, StringPointerToOptional(username));
|
||||
return PreparedQuery{
|
||||
callback.header, std::move(parsed_query.required_privileges),
|
||||
[pull_plan = std::move(pull_plan), callback = std::move(callback), output_symbols = std::move(output_symbols),
|
||||
@@ -1566,8 +1584,7 @@ Callback CreateTrigger(TriggerQuery *trigger_query,
|
||||
interpreter_context->trigger_store.AddTrigger(
|
||||
std::move(trigger_name), trigger_statement, user_parameters, ToTriggerEventType(event_type),
|
||||
before_commit ? TriggerPhase::BEFORE_COMMIT : TriggerPhase::AFTER_COMMIT, &interpreter_context->ast_cache,
|
||||
dba, &interpreter_context->antlr_lock, interpreter_context->config.query, std::move(owner),
|
||||
interpreter_context->auth_checker);
|
||||
dba, interpreter_context->config.query, std::move(owner), interpreter_context->auth_checker);
|
||||
return {};
|
||||
}};
|
||||
}
|
||||
@@ -2123,8 +2140,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
query_execution->summary["cost_estimate"] = 0.0;
|
||||
|
||||
utils::Timer parsing_timer;
|
||||
ParsedQuery parsed_query = ParseQuery(query_string, params, &interpreter_context_->ast_cache,
|
||||
&interpreter_context_->antlr_lock, interpreter_context_->config.query);
|
||||
ParsedQuery parsed_query =
|
||||
ParseQuery(query_string, params, &interpreter_context_->ast_cache, interpreter_context_->config.query);
|
||||
query_execution->summary["parsing_time"] = parsing_timer.Elapsed().count();
|
||||
|
||||
// Some queries require an active transaction in order to be prepared.
|
||||
@@ -2147,7 +2164,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory,
|
||||
&query_execution->notifications,
|
||||
&query_execution->notifications, username,
|
||||
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
|
||||
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
@@ -2155,7 +2172,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
&query_execution->execution_memory_with_exception, username);
|
||||
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory);
|
||||
@@ -2165,7 +2182,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
&query_execution->execution_memory_with_exception, username);
|
||||
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, interpreter_context_->db,
|
||||
@@ -2250,8 +2267,14 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
|
||||
|
||||
trigger_context.AdaptForAccessor(&db_accessor);
|
||||
try {
|
||||
auto owner = trigger.Owner();
|
||||
memgraph::auth::User *user = nullptr;
|
||||
if (owner.has_value()) {
|
||||
user = interpreter_context->auth->GetUser(*owner);
|
||||
}
|
||||
|
||||
trigger.Execute(&db_accessor, &execution_memory, interpreter_context->config.execution_timeout_sec,
|
||||
&interpreter_context->is_shutting_down, trigger_context, interpreter_context->auth_checker);
|
||||
&interpreter_context->is_shutting_down, trigger_context, user, interpreter_context->auth_checker);
|
||||
} catch (const utils::BasicException &exception) {
|
||||
spdlog::warn("Trigger '{}' failed with exception:\n{}", trigger.Name(), exception.what());
|
||||
db_accessor.Abort();
|
||||
@@ -2305,8 +2328,15 @@ void Interpreter::Commit() {
|
||||
utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize};
|
||||
AdvanceCommand();
|
||||
try {
|
||||
auto owner = trigger.Owner();
|
||||
memgraph::auth::User *user = nullptr;
|
||||
if (owner.has_value()) {
|
||||
user = interpreter_context_->auth->GetUser(*owner);
|
||||
}
|
||||
|
||||
trigger.Execute(&*execution_db_accessor_, &execution_memory, interpreter_context_->config.execution_timeout_sec,
|
||||
&interpreter_context_->is_shutting_down, *trigger_context, interpreter_context_->auth_checker);
|
||||
&interpreter_context_->is_shutting_down, *trigger_context, user,
|
||||
interpreter_context_->auth_checker);
|
||||
} catch (const utils::BasicException &e) {
|
||||
throw utils::BasicException(
|
||||
fmt::format("Trigger '{}' caused the transaction to fail.\nException: {}", trigger.Name(), e.what()));
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "auth/models.hpp"
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/config.hpp"
|
||||
#include "query/context.hpp"
|
||||
@@ -99,14 +100,19 @@ class AuthQueryHandler {
|
||||
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
||||
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
||||
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RevokePrivilege(const std::string &user_or_role,
|
||||
const std::vector<AuthQuery::Privilege> &privileges) = 0;
|
||||
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RevokePrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
|
||||
};
|
||||
|
||||
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
|
||||
@@ -173,13 +179,6 @@ struct InterpreterContext {
|
||||
|
||||
storage::Storage *db;
|
||||
|
||||
// ANTLR has singleton instance that is shared between threads. It is
|
||||
// protected by locks inside of ANTLR. Unfortunately, they are not protected
|
||||
// in a very good way. Once we have ANTLR version without race conditions we
|
||||
// can remove this lock. This will probably never happen since ANTLR
|
||||
// developers introduce more bugs in each version. Fortunately, we have
|
||||
// cache so this lock probably won't impact performance much...
|
||||
utils::SpinLock antlr_lock;
|
||||
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
|
||||
std::atomic<bool> is_shutting_down{false};
|
||||
|
||||
|
||||
@@ -31,12 +31,6 @@ class Path {
|
||||
/** Allocator type so that STL containers are aware that we need one */
|
||||
using allocator_type = utils::Allocator<char>;
|
||||
|
||||
/**
|
||||
* Create the path with no elements
|
||||
* Allocations are done using the given MemoryResource.
|
||||
*/
|
||||
explicit Path(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {}
|
||||
|
||||
/**
|
||||
* Create the path starting with the given vertex.
|
||||
* Allocations are done using the given MemoryResource.
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/graph.hpp"
|
||||
#include "query/interpret/eval.hpp"
|
||||
#include "query/path.hpp"
|
||||
#include "query/plan/scoped_profile.hpp"
|
||||
@@ -39,6 +38,7 @@
|
||||
#include "query/procedure/mg_procedure_impl.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/csv_parsing.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
@@ -684,6 +684,13 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
// attempt to get a value from the incoming edges
|
||||
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
|
||||
auto edge = *(*in_edges_it_)++;
|
||||
if (context.auth_checker &&
|
||||
(!context.auth_checker->IsUserAuthorizedEdgeType(context.user, context.db_accessor, edge.EdgeType()) ||
|
||||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
|
||||
edge.To().Labels(storage::View::OLD).GetValue()) ||
|
||||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
|
||||
edge.From().Labels(storage::View::OLD).GetValue())))
|
||||
continue;
|
||||
frame[self_.common_.edge_symbol] = edge;
|
||||
pull_node(edge, EdgeAtom::Direction::IN);
|
||||
return true;
|
||||
@@ -696,6 +703,13 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
// we should do only one expansion for cycles, and it was
|
||||
// already done in the block above
|
||||
if (self_.common_.direction == EdgeAtom::Direction::BOTH && edge.IsCycle()) continue;
|
||||
if (context.auth_checker &&
|
||||
(!context.auth_checker->IsUserAuthorizedEdgeType(context.user, context.db_accessor, edge.EdgeType()) ||
|
||||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
|
||||
edge.To().Labels(storage::View::OLD).GetValue()) ||
|
||||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
|
||||
edge.From().Labels(storage::View::OLD).GetValue())))
|
||||
continue;
|
||||
frame[self_.common_.edge_symbol] = edge;
|
||||
pull_node(edge, EdgeAtom::Direction::OUT);
|
||||
return true;
|
||||
@@ -833,6 +847,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
|
||||
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges)));
|
||||
}
|
||||
}
|
||||
|
||||
if (direction != EdgeAtom::Direction::IN) {
|
||||
auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types));
|
||||
if (edges.begin() != edges.end()) {
|
||||
@@ -1013,8 +1028,6 @@ class ExpandVariableCursor : public Cursor {
|
||||
edges_on_frame.resize(std::min(edges_on_frame.size(), edges_.size()));
|
||||
}
|
||||
|
||||
// if we are here, we have a valid stack,
|
||||
// get the edge, increase the relevant iterator
|
||||
auto current_edge = *edges_it_.back()++;
|
||||
|
||||
// Check edge-uniqueness.
|
||||
@@ -1022,11 +1035,11 @@ class ExpandVariableCursor : public Cursor {
|
||||
std::any_of(edges_on_frame.begin(), edges_on_frame.end(),
|
||||
[¤t_edge](const TypedValue &edge) { return current_edge.first == edge.ValueEdge(); });
|
||||
if (found_existing) continue;
|
||||
|
||||
AppendEdge(current_edge.first, &edges_on_frame);
|
||||
VertexAccessor current_vertex =
|
||||
current_edge.second == EdgeAtom::Direction::IN ? current_edge.first.From() : current_edge.first.To();
|
||||
|
||||
AppendEdge(current_edge.first, &edges_on_frame);
|
||||
|
||||
if (!self_.common_.existing_node) {
|
||||
frame[self_.common_.node_symbol] = current_vertex;
|
||||
}
|
||||
@@ -1363,6 +1376,7 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
|
||||
|
||||
const auto &vertex = vertex_value.ValueVertex();
|
||||
processed_.emplace(vertex, std::nullopt);
|
||||
|
||||
expand_from_vertex(vertex);
|
||||
|
||||
// go back to loop start and see if we expanded anything
|
||||
@@ -2601,19 +2615,17 @@ namespace {
|
||||
* when there are */
|
||||
TypedValue DefaultAggregationOpValue(const Aggregate::Element &element, utils::MemoryResource *memory) {
|
||||
switch (element.op) {
|
||||
case Aggregation::Op::COUNT:
|
||||
return TypedValue(0, memory);
|
||||
case Aggregation::Op::SUM:
|
||||
case Aggregation::Op::MIN:
|
||||
case Aggregation::Op::MAX:
|
||||
case Aggregation::Op::AVG:
|
||||
return TypedValue(memory);
|
||||
case Aggregation::Op::COLLECT_MAP:
|
||||
return TypedValue(TypedValue::TMap(memory));
|
||||
case Aggregation::Op::COUNT:
|
||||
case Aggregation::Op::SUM:
|
||||
return TypedValue(0, memory);
|
||||
case Aggregation::Op::COLLECT_LIST:
|
||||
return TypedValue(TypedValue::TVector(memory));
|
||||
case Aggregation::Op::PROJECT:
|
||||
return TypedValue(query::Graph(memory));
|
||||
case Aggregation::Op::COLLECT_MAP:
|
||||
return TypedValue(TypedValue::TMap(memory));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -2631,9 +2643,7 @@ class AggregateCursor : public Cursor {
|
||||
pulled_all_input_ = true;
|
||||
aggregation_it_ = aggregation_.begin();
|
||||
|
||||
// in case there is no input and no group_bys we need to return true
|
||||
// just this once
|
||||
if (aggregation_.empty() && self_.group_by_.empty()) {
|
||||
if (aggregation_.empty()) {
|
||||
auto *pull_memory = context.evaluation_context.memory;
|
||||
// place default aggregation values on the frame
|
||||
for (const auto &elem : self_.aggregations_)
|
||||
@@ -2778,6 +2788,7 @@ class AggregateCursor : public Cursor {
|
||||
"Expected as much AggregationValue.counts_ as there are "
|
||||
"aggregations.");
|
||||
|
||||
// we iterate over counts, values and aggregation info at the same time
|
||||
auto count_it = agg_value->counts_.begin();
|
||||
auto value_it = agg_value->values_.begin();
|
||||
auto agg_elem_it = self_.aggregations_.begin();
|
||||
@@ -2816,11 +2827,6 @@ class AggregateCursor : public Cursor {
|
||||
case Aggregation::Op::COLLECT_LIST:
|
||||
value_it->ValueList().push_back(input_value);
|
||||
break;
|
||||
case Aggregation::Op::PROJECT: {
|
||||
EnsureOkForProject(input_value);
|
||||
value_it->ValueGraph().Expand(input_value.ValuePath());
|
||||
break;
|
||||
}
|
||||
case Aggregation::Op::COLLECT_MAP:
|
||||
auto key = agg_elem_it->key->Accept(*evaluator);
|
||||
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
|
||||
@@ -2869,11 +2875,6 @@ class AggregateCursor : public Cursor {
|
||||
case Aggregation::Op::COLLECT_LIST:
|
||||
value_it->ValueList().push_back(input_value);
|
||||
break;
|
||||
case Aggregation::Op::PROJECT: {
|
||||
EnsureOkForProject(input_value);
|
||||
value_it->ValueGraph().Expand(input_value.ValuePath());
|
||||
break;
|
||||
}
|
||||
case Aggregation::Op::COLLECT_MAP:
|
||||
auto key = agg_elem_it->key->Accept(*evaluator);
|
||||
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
|
||||
@@ -2910,18 +2911,6 @@ class AggregateCursor : public Cursor {
|
||||
throw QueryRuntimeException("Only numeric values allowed in SUM and AVG aggregations.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if the given TypedValue is legal in PROJECT and PROJECT_TRANSITIVE. If not
|
||||
* an appropriate exception is thrown. */
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
void EnsureOkForProject(const TypedValue &value) const {
|
||||
switch (value.type()) {
|
||||
case TypedValue::Type::Path:
|
||||
return;
|
||||
default:
|
||||
throw QueryRuntimeException("Only path values allowed in PROJECT aggregation.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UniqueCursorPtr Aggregate::MakeCursor(utils::MemoryResource *mem) const {
|
||||
@@ -3734,15 +3723,6 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name,
|
||||
for (auto *expression : args) {
|
||||
args_list.emplace_back(expression->Accept(*evaluator));
|
||||
}
|
||||
|
||||
if (!args_list.empty() && args_list.front().type() == TypedValue::Type::Graph) {
|
||||
auto *subgraph =
|
||||
new query::Graph(std::move(args_list.front().ValueGraph()), args_list.front().ValueGraph().GetMemoryResource());
|
||||
args_list.erase(args_list.begin());
|
||||
|
||||
graph.impl = query::SubgraphDbAccessor::MakeSubgraphDbAccessor(std::get<query::DbAccessor *>(graph.impl), subgraph);
|
||||
}
|
||||
|
||||
procedure::ConstructArguments(args_list, proc, fully_qualified_procedure_name, proc_args, graph);
|
||||
if (memory_limit) {
|
||||
SPDLOG_INFO("Running '{}' with memory limit of {}", fully_qualified_procedure_name,
|
||||
|
||||
@@ -38,9 +38,6 @@
|
||||
#include "utils/temporal.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
#include <cppitertools/filter.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
|
||||
// This file contains implementation of top level C API functions, but this is
|
||||
// all actually part of memgraph::query::procedure. So use that namespace for simplicity.
|
||||
// NOLINTNEXTLINE(google-build-using-namespace)
|
||||
@@ -293,8 +290,6 @@ mgp_value_type FromTypedValueType(memgraph::query::TypedValue::Type type) {
|
||||
return MGP_VALUE_TYPE_LOCAL_DATE_TIME;
|
||||
case memgraph::query::TypedValue::Type::Duration:
|
||||
return MGP_VALUE_TYPE_DURATION;
|
||||
case memgraph::query::TypedValue::Type::Graph:
|
||||
throw std::logic_error{"No graph type"};
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -329,19 +324,17 @@ memgraph::query::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils::
|
||||
return memgraph::query::TypedValue(std::move(tv_map));
|
||||
}
|
||||
case MGP_VALUE_TYPE_VERTEX:
|
||||
return memgraph::query::TypedValue(val.vertex_v->getImpl(), memory);
|
||||
return memgraph::query::TypedValue(val.vertex_v->impl, memory);
|
||||
case MGP_VALUE_TYPE_EDGE:
|
||||
return memgraph::query::TypedValue(val.edge_v->impl, memory);
|
||||
case MGP_VALUE_TYPE_PATH: {
|
||||
const auto *path = val.path_v;
|
||||
MG_ASSERT(!path->vertices.empty());
|
||||
MG_ASSERT(path->vertices.size() == path->edges.size() + 1);
|
||||
|
||||
;
|
||||
memgraph::query::Path tv_path(path->vertices[0].getImpl(), memory);
|
||||
memgraph::query::Path tv_path(path->vertices[0].impl, memory);
|
||||
for (size_t i = 0; i < path->edges.size(); ++i) {
|
||||
tv_path.Expand(path->edges[i].impl);
|
||||
tv_path.Expand(path->vertices[i + 1].getImpl());
|
||||
tv_path.Expand(path->vertices[i + 1].impl);
|
||||
}
|
||||
return memgraph::query::TypedValue(std::move(tv_path));
|
||||
}
|
||||
@@ -461,31 +454,12 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
|
||||
}
|
||||
case MGP_VALUE_TYPE_VERTEX: {
|
||||
memgraph::utils::Allocator<mgp_vertex> allocator(m);
|
||||
vertex_v = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *) { return allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph); },
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
return allocator.new_object<mgp_vertex>(
|
||||
memgraph::query::SubgraphVertexAccessor(tv.ValueVertex(), impl->getGraph()), graph);
|
||||
}},
|
||||
graph->impl);
|
||||
|
||||
vertex_v = allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph);
|
||||
break;
|
||||
}
|
||||
case MGP_VALUE_TYPE_EDGE: {
|
||||
memgraph::utils::Allocator<mgp_edge> allocator(m);
|
||||
|
||||
edge_v = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[&tv, graph, &allocator](memgraph::query::DbAccessor *) {
|
||||
return allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
|
||||
},
|
||||
[&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) {
|
||||
return allocator.new_object<mgp_edge>(
|
||||
tv.ValueEdge(), memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().From(), db_impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().To(), db_impl->getGraph()), graph);
|
||||
}},
|
||||
graph->impl);
|
||||
edge_v = allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
|
||||
break;
|
||||
}
|
||||
case MGP_VALUE_TYPE_PATH: {
|
||||
@@ -496,24 +470,11 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
|
||||
mgp_path tmp_path(m);
|
||||
tmp_path.vertices.reserve(tv.ValuePath().vertices().size());
|
||||
for (const auto &v : tv.ValuePath().vertices()) {
|
||||
std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[&v, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.vertices.emplace_back(v, graph); },
|
||||
[&v, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
tmp_path.vertices.emplace_back(memgraph::query::SubgraphVertexAccessor(v, impl->getGraph()), graph);
|
||||
}},
|
||||
graph->impl);
|
||||
tmp_path.vertices.emplace_back(v, graph);
|
||||
}
|
||||
tmp_path.edges.reserve(tv.ValuePath().edges().size());
|
||||
for (const auto &e : tv.ValuePath().edges()) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[&e, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.edges.emplace_back(e, graph); },
|
||||
[&e, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *db_impl) {
|
||||
tmp_path.edges.emplace_back(
|
||||
e, memgraph::query::SubgraphVertexAccessor(e.From(), db_impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor(e.To(), db_impl->getGraph()), graph);
|
||||
}},
|
||||
graph->impl);
|
||||
tmp_path.edges.emplace_back(e, graph);
|
||||
}
|
||||
memgraph::utils::Allocator<mgp_path> allocator(m);
|
||||
path_v = allocator.new_object<mgp_path>(std::move(tmp_path));
|
||||
@@ -838,15 +799,7 @@ mgp_value::mgp_value(mgp_value &&other, memgraph::utils::MemoryResource *m) : ty
|
||||
mgp_value::~mgp_value() noexcept { DeleteValueMember(this); }
|
||||
|
||||
mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) {
|
||||
return std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *) { return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph); },
|
||||
[&](memgraph::query::SubgraphDbAccessor *db_impl) {
|
||||
return NewRawMgpObject<mgp_edge>(
|
||||
&memory, edge.impl, memgraph::query::SubgraphVertexAccessor(edge.impl.From(), db_impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor(edge.impl.To(), db_impl->getGraph()), edge.to.graph);
|
||||
}},
|
||||
edge.to.graph->impl);
|
||||
return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph);
|
||||
}
|
||||
|
||||
void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); }
|
||||
@@ -1177,7 +1130,7 @@ mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) {
|
||||
}
|
||||
const auto *start1 = Call<mgp_vertex *>(mgp_path_vertex_at, p1, 0);
|
||||
const auto *start2 = Call<mgp_vertex *>(mgp_path_vertex_at, p2, 0);
|
||||
static_assert(noexcept(start1 == start2));
|
||||
static_assert(noexcept(start1->impl == start2->impl));
|
||||
if (*start1 != *start2) {
|
||||
return 0;
|
||||
}
|
||||
@@ -1544,15 +1497,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
|
||||
return nullptr;
|
||||
}
|
||||
memgraph::utils::OnScopeExit clean_up([it] { it->current = std::nullopt; });
|
||||
auto propToName = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[it](auto *impl) {
|
||||
return memgraph::utils::pmr::string(impl->PropertyToName(it->current_it->first),
|
||||
it->GetMemoryResource());
|
||||
},
|
||||
},
|
||||
it->graph->impl);
|
||||
it->current.emplace(propToName, mgp_value(it->current_it->second, it->GetMemoryResource()));
|
||||
it->current.emplace(memgraph::utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first),
|
||||
it->GetMemoryResource()),
|
||||
mgp_value(it->current_it->second, it->GetMemoryResource()));
|
||||
it->property.name = it->current->first.c_str();
|
||||
it->property.value = &it->current->second;
|
||||
clean_up.Disable();
|
||||
@@ -1562,12 +1509,7 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
|
||||
}
|
||||
|
||||
mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) {
|
||||
return WrapExceptions(
|
||||
[v] {
|
||||
return mgp_vertex_id{
|
||||
.as_int = std::visit(memgraph::utils::Overloaded{[](auto &impl) { return impl.Gid().AsInt(); }}, v->impl)};
|
||||
},
|
||||
result);
|
||||
return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result);
|
||||
}
|
||||
|
||||
mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
|
||||
@@ -1639,15 +1581,8 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
|
||||
}
|
||||
|
||||
const auto prop_key = std::visit(
|
||||
memgraph::utils::Overloaded{[property_name](auto *impl) { return impl->NameToProperty(property_name); }},
|
||||
v->graph->impl);
|
||||
|
||||
const auto result = std::visit(memgraph::utils::Overloaded{[prop_key, property_value](auto &impl) {
|
||||
return impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
}},
|
||||
v->impl);
|
||||
const auto prop_key = v->graph->impl->NameToProperty(property_name);
|
||||
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1673,11 +1608,11 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
}
|
||||
const auto old_value = memgraph::query::TypedValue(*result);
|
||||
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
|
||||
trigger_ctx_collector->RegisterRemovedObjectProperty(v->getImpl(), prop_key, old_value);
|
||||
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
|
||||
return;
|
||||
}
|
||||
const auto new_value = ToTypedValue(*property_value, property_value->memory);
|
||||
trigger_ctx_collector->RegisterSetObjectProperty(v->getImpl(), prop_key, old_value, new_value);
|
||||
trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1686,18 +1621,8 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
|
||||
}
|
||||
const auto label_id = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](auto *impl) { return impl->NameToLabel(label.name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
|
||||
const auto result = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label_id](auto &impl) { return impl.AddLabel(label_id); },
|
||||
|
||||
},
|
||||
v->impl);
|
||||
const auto label_id = v->graph->impl->NameToLabel(label.name);
|
||||
const auto result = v->impl.AddLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -1718,7 +1643,7 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id);
|
||||
ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1728,17 +1653,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
|
||||
}
|
||||
const auto label_id = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](auto *impl) { return impl->NameToLabel(label.name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
const auto result = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label_id](auto &impl) { return impl.RemoveLabel(label_id); },
|
||||
|
||||
},
|
||||
v->impl);
|
||||
const auto label_id = v->graph->impl->NameToLabel(label.name);
|
||||
const auto result = v->impl.RemoveLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -1759,7 +1675,7 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id);
|
||||
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1780,8 +1696,7 @@ mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
|
||||
mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
|
||||
return WrapExceptions(
|
||||
[v]() -> size_t {
|
||||
auto maybe_labels = std::visit(
|
||||
memgraph::utils::Overloaded{[v](const auto &impl) { return impl.Labels(v->graph->view); }}, v->impl);
|
||||
auto maybe_labels = v->impl.Labels(v->graph->view);
|
||||
if (maybe_labels.HasError()) {
|
||||
switch (maybe_labels.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1803,11 +1718,7 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
|
||||
return WrapExceptions(
|
||||
[v, i]() -> const char * {
|
||||
// TODO: Maybe it's worth caching this in mgp_vertex.
|
||||
auto maybe_labels = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v](const auto &impl) { return impl.Labels(v->graph->view); },
|
||||
},
|
||||
v->impl);
|
||||
auto maybe_labels = v->impl.Labels(v->graph->view);
|
||||
if (maybe_labels.HasError()) {
|
||||
switch (maybe_labels.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1824,16 +1735,10 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
|
||||
throw std::out_of_range("Label cannot be retrieved, because index exceeds the number of labels!");
|
||||
}
|
||||
const auto &label = (*maybe_labels)[i];
|
||||
static_assert(std::is_lvalue_reference_v<
|
||||
decltype(std::get<memgraph::query::DbAccessor *>(v->graph->impl)->LabelToName(label))>,
|
||||
static_assert(std::is_lvalue_reference_v<decltype(v->graph->impl->LabelToName(label))>,
|
||||
"Expected LabelToName to return a pointer or reference, so we "
|
||||
"don't have to take a copy and manage memory.");
|
||||
|
||||
const auto &name = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[label](const auto *impl) { return impl->LabelToName(label); },
|
||||
},
|
||||
v->graph->impl);
|
||||
const auto &name = v->graph->impl->LabelToName(label);
|
||||
return name.c_str();
|
||||
},
|
||||
&result->name);
|
||||
@@ -1843,17 +1748,9 @@ mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *resul
|
||||
return WrapExceptions(
|
||||
[v, name] {
|
||||
memgraph::storage::LabelId label;
|
||||
label = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[name](auto *impl) { return impl->NameToLabel(name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
label = v->graph->impl->NameToLabel(name);
|
||||
|
||||
auto maybe_has_label = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v, label](auto &impl) { return impl.HasLabel(v->graph->view, label); },
|
||||
},
|
||||
v->impl);
|
||||
auto maybe_has_label = v->impl.HasLabel(v->graph->view, label);
|
||||
if (maybe_has_label.HasError()) {
|
||||
switch (maybe_has_label.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1881,17 +1778,8 @@ mgp_error mgp_vertex_has_label(mgp_vertex *v, mgp_label label, int *result) {
|
||||
mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) {
|
||||
return WrapExceptions(
|
||||
[v, name, memory]() -> mgp_value * {
|
||||
const auto &key = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[name](auto *impl) { return impl->NameToProperty(name); },
|
||||
},
|
||||
v->graph->impl);
|
||||
|
||||
auto maybe_prop = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v, key](auto &impl) { return impl.GetProperty(v->graph->view, key); },
|
||||
},
|
||||
v->impl);
|
||||
const auto &key = v->graph->impl->NameToProperty(name);
|
||||
auto maybe_prop = v->impl.GetProperty(v->graph->view, key);
|
||||
if (maybe_prop.HasError()) {
|
||||
switch (maybe_prop.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1917,11 +1805,7 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
|
||||
// will probably require a different API in storage.
|
||||
return WrapExceptions(
|
||||
[v, memory] {
|
||||
auto maybe_props = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v](auto &impl) { return impl.Properties(v->graph->view); },
|
||||
},
|
||||
v->impl);
|
||||
auto maybe_props = v->impl.Properties(v->graph->view);
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1949,11 +1833,7 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
|
||||
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
|
||||
MG_ASSERT(it != nullptr);
|
||||
|
||||
auto maybe_edges = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v](auto &impl) { return impl.InEdges(v->graph->view); },
|
||||
},
|
||||
v->impl);
|
||||
auto maybe_edges = v->impl.InEdges(v->graph->view);
|
||||
if (maybe_edges.HasError()) {
|
||||
switch (maybe_edges.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -1971,19 +1851,7 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
|
||||
it->in.emplace(std::move(*maybe_edges));
|
||||
it->in_it.emplace(it->in->begin());
|
||||
if (*it->in_it != it->in->end()) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *) {
|
||||
it->current_e.emplace(**it->in_it, (**it->in_it).From(), (**it->in_it).To(), v->graph,
|
||||
it->GetMemoryResource());
|
||||
},
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
it->current_e.emplace(
|
||||
**it->in_it,
|
||||
memgraph::query::SubgraphVertexAccessor((**it->in_it).From(), impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor((**it->in_it).To(), impl->getGraph()), v->graph,
|
||||
it->GetMemoryResource());
|
||||
}},
|
||||
v->graph->impl);
|
||||
it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource());
|
||||
}
|
||||
|
||||
return it.release();
|
||||
@@ -1996,12 +1864,8 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
|
||||
[v, memory] {
|
||||
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
|
||||
MG_ASSERT(it != nullptr);
|
||||
auto maybe_edges = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[v](auto &impl) { return impl.OutEdges(v->graph->view); },
|
||||
},
|
||||
v->impl);
|
||||
|
||||
auto maybe_edges = v->impl.OutEdges(v->graph->view);
|
||||
if (maybe_edges.HasError()) {
|
||||
switch (maybe_edges.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -2016,23 +1880,10 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
|
||||
LOG_FATAL("Unexpected error when getting the outbound edges of a vertex.");
|
||||
}
|
||||
}
|
||||
|
||||
it->out.emplace(std::move(*maybe_edges));
|
||||
it->out_it.emplace(it->out->begin());
|
||||
if (*it->out_it != it->out->end()) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *) {
|
||||
it->current_e.emplace(**it->out_it, (**it->out_it).From(), (**it->out_it).To(), v->graph,
|
||||
it->GetMemoryResource());
|
||||
},
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
it->current_e.emplace(
|
||||
**it->out_it,
|
||||
memgraph::query::SubgraphVertexAccessor((**it->out_it).From(), impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor((**it->out_it).To(), impl->getGraph()), v->graph,
|
||||
it->GetMemoryResource());
|
||||
}},
|
||||
v->graph->impl);
|
||||
it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource());
|
||||
}
|
||||
|
||||
return it.release();
|
||||
@@ -2070,19 +1921,7 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
|
||||
it->current_e = std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *) {
|
||||
it->current_e.emplace(**impl_it, (**impl_it).From(), (**impl_it).To(),
|
||||
it->source_vertex.graph, it->GetMemoryResource());
|
||||
},
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
it->current_e.emplace(
|
||||
**impl_it, memgraph::query::SubgraphVertexAccessor((**impl_it).From(), impl->getGraph()),
|
||||
memgraph::query::SubgraphVertexAccessor((**impl_it).To(), impl->getGraph()),
|
||||
it->source_vertex.graph, it->GetMemoryResource());
|
||||
}},
|
||||
it->source_vertex.graph->impl);
|
||||
|
||||
it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource());
|
||||
return &*it->current_e;
|
||||
};
|
||||
if (it->in_it) {
|
||||
@@ -2117,14 +1956,10 @@ mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
|
||||
mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
|
||||
return WrapExceptions(
|
||||
[e] {
|
||||
const auto &name = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[e](const auto *impl) { return impl->EdgeTypeToName(e->impl.EdgeType()); },
|
||||
},
|
||||
e->from.graph->impl);
|
||||
// static_assert(std::is_lvalue_reference_v<decltype(e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>,
|
||||
// "Expected EdgeTypeToName to return a pointer or reference, so we "
|
||||
// "don't have to take a copy and manage memory.");
|
||||
const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
|
||||
static_assert(std::is_lvalue_reference_v<decltype(e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>,
|
||||
"Expected EdgeTypeToName to return a pointer or reference, so we "
|
||||
"don't have to take a copy and manage memory.");
|
||||
return name.c_str();
|
||||
},
|
||||
&result->name);
|
||||
@@ -2143,11 +1978,7 @@ mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) {
|
||||
mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
|
||||
return WrapExceptions(
|
||||
[e, name, memory] {
|
||||
const auto &key = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[name](auto *impl) { return impl->NameToProperty(name); },
|
||||
},
|
||||
e->from.graph->impl);
|
||||
const auto &key = e->from.graph->impl->NameToProperty(name);
|
||||
auto view = e->from.graph->view;
|
||||
auto maybe_prop = e->impl.GetProperty(view, key);
|
||||
if (maybe_prop.HasError()) {
|
||||
@@ -2173,11 +2004,7 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
|
||||
if (!MgpEdgeIsMutable(*e)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
|
||||
}
|
||||
const auto prop_key = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[property_name](auto *impl) { return impl->NameToProperty(property_name); },
|
||||
},
|
||||
e->from.graph->impl);
|
||||
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
|
||||
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
|
||||
if (result.HasError()) {
|
||||
@@ -2244,24 +2071,9 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
|
||||
mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
|
||||
return WrapExceptions(
|
||||
[graph, id, memory]() -> mgp_vertex * {
|
||||
std::optional<memgraph::query::VertexAccessor> maybe_vertex = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[graph, id](auto *impl) {
|
||||
return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
|
||||
},
|
||||
},
|
||||
graph->impl);
|
||||
auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
|
||||
if (maybe_vertex) {
|
||||
return std::visit(memgraph::utils::Overloaded{
|
||||
[memory, graph, maybe_vertex](memgraph::query::DbAccessor *) {
|
||||
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
},
|
||||
[memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
return NewRawMgpObject<mgp_vertex>(
|
||||
memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()),
|
||||
graph);
|
||||
}},
|
||||
graph->impl);
|
||||
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
|
||||
}
|
||||
return nullptr;
|
||||
},
|
||||
@@ -2279,18 +2091,15 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
|
||||
}
|
||||
auto *vertex = std::visit(memgraph::utils::Overloaded{[=](auto *impl) {
|
||||
return NewRawMgpObject<mgp_vertex>(memory, impl->InsertVertex(), graph);
|
||||
}},
|
||||
graph->impl);
|
||||
auto vertex = graph->impl->InsertVertex();
|
||||
|
||||
auto &ctx = graph->ctx;
|
||||
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(vertex->getImpl());
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(vertex);
|
||||
}
|
||||
return vertex;
|
||||
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
|
||||
},
|
||||
result);
|
||||
}
|
||||
@@ -2300,24 +2109,7 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
|
||||
}
|
||||
|
||||
const auto result =
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[&](memgraph::query::DbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{
|
||||
"Remove vertex for DbAccessor should not get reference to SubgraphVertexAccessor"};
|
||||
}
|
||||
return impl->RemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
},
|
||||
[&](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{
|
||||
"Remove vertex for SubgraphDbAccessor should not get reference to VertexAccessor"};
|
||||
}
|
||||
return impl->RemoveVertex(&(std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl)));
|
||||
}},
|
||||
graph->impl);
|
||||
const auto result = graph->impl->RemoveVertex(&vertex->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -2352,23 +2144,7 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
|
||||
}
|
||||
const auto result =
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[vertex](memgraph::query::DbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{"Wrong type"};
|
||||
}
|
||||
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
},
|
||||
[vertex](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
// todo antoniofilipovic change this, it is wrong here since it needs to be
|
||||
// SubgraphVertexAccessor
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(vertex->impl)) {
|
||||
throw std::logic_error{"Wrong type"};
|
||||
}
|
||||
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
|
||||
}},
|
||||
graph->impl);
|
||||
const auto result = graph->impl->DetachRemoveVertex(&vertex->impl);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -2415,28 +2191,8 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
|
||||
}
|
||||
auto edge =
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[from, to, type](memgraph::query::DbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(from->impl) ||
|
||||
std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(to->impl)) {
|
||||
throw std::logic_error{"Both vertices must be of VertexAccessor type"};
|
||||
}
|
||||
return impl->InsertEdge(&std::get<memgraph::query::VertexAccessor>(from->impl),
|
||||
&std::get<memgraph::query::VertexAccessor>(to->impl),
|
||||
impl->NameToEdgeType(type.name));
|
||||
},
|
||||
[from, to, type](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(from->impl) ||
|
||||
std::holds_alternative<memgraph::query::VertexAccessor>(to->impl)) {
|
||||
throw std::logic_error{"Both vertices must be of SubgraphVertexAccessor type"};
|
||||
}
|
||||
return impl->InsertEdge(&std::get<memgraph::query::SubgraphVertexAccessor>(from->impl),
|
||||
&std::get<memgraph::query::SubgraphVertexAccessor>(to->impl),
|
||||
impl->NameToEdgeType(type.name));
|
||||
}},
|
||||
graph->impl);
|
||||
|
||||
auto edge = graph->impl->InsertEdge(&from->impl, &to->impl, from->graph->impl->NameToEdgeType(type.name));
|
||||
if (edge.HasError()) {
|
||||
switch (edge.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -2457,18 +2213,7 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(*edge);
|
||||
}
|
||||
return std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[memory, edge, from](memgraph::query::DbAccessor *) {
|
||||
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), from->graph);
|
||||
},
|
||||
[memory, edge, from](memgraph::query::SubgraphDbAccessor *db_impl) {
|
||||
const auto &v_from =
|
||||
memgraph::query::SubgraphVertexAccessor(edge.GetValue().From(), db_impl->getGraph());
|
||||
const auto &v_to = memgraph::query::SubgraphVertexAccessor(edge.GetValue().To(), db_impl->getGraph());
|
||||
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), v_from, v_to, from->graph);
|
||||
}},
|
||||
graph->impl);
|
||||
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
|
||||
},
|
||||
result);
|
||||
}
|
||||
@@ -2478,9 +2223,8 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
|
||||
}
|
||||
const auto result = graph->impl->RemoveEdge(&edge->impl);
|
||||
|
||||
const auto result = std::visit(
|
||||
memgraph::utils::Overloaded{[edge](auto *impl) { return impl->RemoveEdge(&edge->impl); }}, graph->impl);
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::storage::Error::NONEXISTENT_OBJECT:
|
||||
@@ -2541,18 +2285,7 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res
|
||||
return nullptr;
|
||||
}
|
||||
memgraph::utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; });
|
||||
std::visit(memgraph::utils::Overloaded{[it](memgraph::query::DbAccessor *) {
|
||||
it->current_v.emplace(*it->current_it, it->graph,
|
||||
it->GetMemoryResource());
|
||||
;
|
||||
},
|
||||
[it](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
it->current_v.emplace(memgraph::query::SubgraphVertexAccessor(
|
||||
*it->current_it, impl->getGraph()),
|
||||
it->graph, it->GetMemoryResource());
|
||||
}},
|
||||
it->graph->impl);
|
||||
|
||||
it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource());
|
||||
clean_up.Disable();
|
||||
return &*it->current_v;
|
||||
},
|
||||
@@ -2835,7 +2568,6 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) {
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
case TypedValue::Type::Graph:
|
||||
LOG_FATAL("value must not be a graph element");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "utils/pmr/string.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
/// Wraps memory resource used in custom procedures.
|
||||
///
|
||||
/// This should have been `using mgp_memory = memgraph::utils::MemoryResource`, but that's
|
||||
@@ -443,69 +442,21 @@ struct mgp_vertex {
|
||||
mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(v), graph(graph) {}
|
||||
|
||||
mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(v), graph(graph) {}
|
||||
|
||||
mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
other.impl);
|
||||
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
this->impl);
|
||||
}
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
mgp_vertex(mgp_vertex &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
other.impl);
|
||||
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[](memgraph::query::VertexAccessor) { std::cout << "VertexAccessor" << std::endl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor) { std::cout << "SubgraphVertexAccessor" << std::endl; }},
|
||||
this->impl);
|
||||
}
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
memgraph::query::VertexAccessor getImpl() const {
|
||||
return std::visit(
|
||||
memgraph::utils::Overloaded{[](memgraph::query::VertexAccessor impl) { return impl; },
|
||||
[](memgraph::query::SubgraphVertexAccessor impl) { return impl.impl_; }},
|
||||
this->impl);
|
||||
}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_vertex(const mgp_vertex &) = delete;
|
||||
|
||||
mgp_vertex &operator=(const mgp_vertex &) = delete;
|
||||
mgp_vertex &operator=(mgp_vertex &&) = delete;
|
||||
|
||||
bool operator==(const mgp_vertex &other) const noexcept {
|
||||
return std::visit(memgraph::utils::Overloaded{
|
||||
[&other](memgraph::query::VertexAccessor impl) {
|
||||
if (std::holds_alternative<memgraph::query::SubgraphVertexAccessor>(other.impl)) {
|
||||
return false;
|
||||
}
|
||||
return impl == std::get<memgraph::query::VertexAccessor>(other.impl);
|
||||
},
|
||||
[&other](memgraph::query::SubgraphVertexAccessor impl) -> bool {
|
||||
if (std::holds_alternative<memgraph::query::VertexAccessor>(other.impl)) {
|
||||
return false;
|
||||
}
|
||||
return impl == std::get<memgraph::query::SubgraphVertexAccessor>(other.impl);
|
||||
},
|
||||
},
|
||||
this->impl);
|
||||
}
|
||||
|
||||
bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; }
|
||||
bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); };
|
||||
|
||||
~mgp_vertex() = default;
|
||||
@@ -513,38 +464,10 @@ struct mgp_vertex {
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
std::variant<memgraph::query::VertexAccessor, memgraph::query::SubgraphVertexAccessor> impl;
|
||||
memgraph::query::VertexAccessor impl;
|
||||
mgp_graph *graph;
|
||||
};
|
||||
|
||||
struct mgp_graph {
|
||||
std::variant<memgraph::query::DbAccessor *, memgraph::query::SubgraphDbAccessor *> impl;
|
||||
memgraph::storage::View view;
|
||||
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
|
||||
// `ctx` field is out of place here.
|
||||
memgraph::query::ExecutionContext *ctx;
|
||||
|
||||
// memgraph:::query::Graph *subraph;
|
||||
|
||||
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
|
||||
memgraph::query::ExecutionContext &ctx) {
|
||||
return mgp_graph{&acc, view, &ctx};
|
||||
}
|
||||
|
||||
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
|
||||
return mgp_graph{&acc, view, nullptr};
|
||||
}
|
||||
|
||||
static mgp_graph WritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view,
|
||||
memgraph::query::ExecutionContext &ctx) {
|
||||
return mgp_graph{&acc, view, &ctx};
|
||||
}
|
||||
|
||||
static mgp_graph NonWritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view) {
|
||||
return mgp_graph{&acc, view, nullptr};
|
||||
}
|
||||
};
|
||||
|
||||
struct mgp_edge {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
@@ -561,16 +484,6 @@ struct mgp_edge {
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {}
|
||||
|
||||
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::VertexAccessor &from_v,
|
||||
const memgraph::query::VertexAccessor &to_v, mgp_graph *graph,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
|
||||
|
||||
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::SubgraphVertexAccessor &from_v,
|
||||
const memgraph::query::SubgraphVertexAccessor &to_v, mgp_graph *graph,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
|
||||
|
||||
mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {}
|
||||
|
||||
@@ -657,6 +570,23 @@ struct mgp_func_result {
|
||||
std::optional<memgraph::utils::pmr::string> error_msg;
|
||||
};
|
||||
|
||||
struct mgp_graph {
|
||||
memgraph::query::DbAccessor *impl;
|
||||
memgraph::storage::View view;
|
||||
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
|
||||
// `ctx` field is out of place here.
|
||||
memgraph::query::ExecutionContext *ctx;
|
||||
|
||||
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
|
||||
memgraph::query::ExecutionContext &ctx) {
|
||||
return mgp_graph{&acc, view, &ctx};
|
||||
}
|
||||
|
||||
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
|
||||
return mgp_graph{&acc, view, nullptr};
|
||||
}
|
||||
};
|
||||
|
||||
// Prevents user to use ExecutionContext in writable callables
|
||||
struct mgp_func_context {
|
||||
memgraph::query::DbAccessor *impl;
|
||||
@@ -685,18 +615,8 @@ struct mgp_properties_iterator {
|
||||
mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) {
|
||||
if (current_it != this->pvs.end()) {
|
||||
auto value =
|
||||
std::visit(memgraph::utils::Overloaded{
|
||||
[this, memory](const memgraph::query::DbAccessor *impl) {
|
||||
return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory);
|
||||
},
|
||||
[this, memory](const memgraph::query::SubgraphDbAccessor *impl) {
|
||||
return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory);
|
||||
},
|
||||
},
|
||||
graph->impl);
|
||||
|
||||
current.emplace(value, mgp_value(current_it->second, memory));
|
||||
current.emplace(memgraph::utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory),
|
||||
mgp_value(current_it->second, memory));
|
||||
property.name = current->first.c_str();
|
||||
property.value = ¤t->second;
|
||||
}
|
||||
@@ -715,6 +635,7 @@ struct mgp_properties_iterator {
|
||||
|
||||
struct mgp_edges_iterator {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>;
|
||||
|
||||
// Hopefully mgp_vertex copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>);
|
||||
@@ -741,14 +662,9 @@ struct mgp_edges_iterator {
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_vertex source_vertex;
|
||||
|
||||
std::optional<std::remove_reference_t<
|
||||
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).InEdges(source_vertex.graph->view))>>
|
||||
in;
|
||||
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.InEdges(source_vertex.graph->view))>> in;
|
||||
std::optional<decltype(in->begin())> in_it;
|
||||
std::optional<std::remove_reference_t<
|
||||
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).InEdges(source_vertex.graph->view))>>
|
||||
out;
|
||||
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.OutEdges(source_vertex.graph->view))>> out;
|
||||
std::optional<decltype(out->begin())> out_it;
|
||||
std::optional<mgp_edge> current_e;
|
||||
};
|
||||
@@ -758,20 +674,9 @@ struct mgp_vertices_iterator {
|
||||
|
||||
/// @throw anything VerticesIterable may throw
|
||||
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory),
|
||||
graph(graph),
|
||||
vertices(std::visit(memgraph::utils::Overloaded{[graph](auto *impl) { return impl->Vertices(graph->view); }},
|
||||
graph->impl)),
|
||||
current_it(vertices.begin()) {
|
||||
: memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) {
|
||||
if (current_it != vertices.end()) {
|
||||
std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); },
|
||||
[this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) {
|
||||
current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph,
|
||||
memory);
|
||||
}},
|
||||
graph->impl);
|
||||
current_v.emplace(*current_it, graph, memory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,7 +684,7 @@ struct mgp_vertices_iterator {
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_graph *graph;
|
||||
memgraph::query::VerticesIterable vertices;
|
||||
decltype(graph->impl->Vertices(graph->view)) vertices;
|
||||
decltype(vertices.begin()) current_it;
|
||||
std::optional<mgp_vertex> current_v;
|
||||
};
|
||||
|
||||
@@ -235,7 +235,6 @@ void ConstructArguments(const std::vector<TypedValue> &args, const TCall &callab
|
||||
throw QueryRuntimeException("'{}' argument named '{}' at position {} must be of type {}.", fully_qualified_name,
|
||||
name, i, type->GetPresentableName());
|
||||
}
|
||||
// why do we move graph here?
|
||||
args_list.elems.emplace_back(std::move(arg), &graph);
|
||||
}
|
||||
// Fill missing optional arguments with their default values.
|
||||
|
||||
@@ -153,10 +153,10 @@ std::vector<std::pair<Identifier, TriggerIdentifierTag>> GetPredefinedIdentifier
|
||||
Trigger::Trigger(std::string name, const std::string &query,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||
const TriggerEventType event_type, utils::SkipList<QueryCacheEntry> *query_cache,
|
||||
DbAccessor *db_accessor, utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
|
||||
DbAccessor *db_accessor, const InterpreterConfig::Query &query_config,
|
||||
std::optional<std::string> owner, const query::AuthChecker *auth_checker)
|
||||
: name_{std::move(name)},
|
||||
parsed_statements_{ParseQuery(query, user_parameters, query_cache, antlr_lock, query_config)},
|
||||
parsed_statements_{ParseQuery(query, user_parameters, query_cache, query_config)},
|
||||
event_type_{event_type},
|
||||
owner_{std::move(owner)} {
|
||||
// We check immediately if the query is valid by trying to create a plan.
|
||||
@@ -195,7 +195,7 @@ std::shared_ptr<Trigger::TriggerPlan> Trigger::GetPlan(DbAccessor *db_accessor,
|
||||
|
||||
void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution_memory,
|
||||
const double max_execution_time_sec, std::atomic<bool> *is_shutting_down,
|
||||
const TriggerContext &context, const AuthChecker *auth_checker) const {
|
||||
const TriggerContext &context, memgraph::auth::User *user, AuthChecker *auth_checker) const {
|
||||
if (!context.ShouldEventTrigger(event_type_)) {
|
||||
return;
|
||||
}
|
||||
@@ -215,6 +215,8 @@ void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution
|
||||
ctx.timer = utils::AsyncTimer(max_execution_time_sec);
|
||||
ctx.is_shutting_down = is_shutting_down;
|
||||
ctx.is_profile_query = false;
|
||||
ctx.user = user;
|
||||
ctx.auth_checker = auth_checker;
|
||||
|
||||
// Set up temporary memory for a single Pull. Initial memory comes from the
|
||||
// stack. 256 KiB should fit on the stack and should be more than enough for a
|
||||
@@ -257,7 +259,7 @@ inline constexpr uint64_t kVersion{2};
|
||||
TriggerStore::TriggerStore(std::filesystem::path directory) : storage_{std::move(directory)} {}
|
||||
|
||||
void TriggerStore::RestoreTriggers(utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
|
||||
utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
|
||||
const InterpreterConfig::Query &query_config,
|
||||
const query::AuthChecker *auth_checker) {
|
||||
MG_ASSERT(before_commit_triggers_.size() == 0 && after_commit_triggers_.size() == 0,
|
||||
"Cannot restore trigger when some triggers already exist!");
|
||||
@@ -317,8 +319,8 @@ void TriggerStore::RestoreTriggers(utils::SkipList<QueryCacheEntry> *query_cache
|
||||
|
||||
std::optional<Trigger> trigger;
|
||||
try {
|
||||
trigger.emplace(trigger_name, statement, user_parameters, event_type, query_cache, db_accessor, antlr_lock,
|
||||
query_config, std::move(owner), auth_checker);
|
||||
trigger.emplace(trigger_name, statement, user_parameters, event_type, query_cache, db_accessor, query_config,
|
||||
std::move(owner), auth_checker);
|
||||
} catch (const utils::BasicException &e) {
|
||||
spdlog::warn("Failed to create trigger '{}' because: {}", trigger_name, e.what());
|
||||
continue;
|
||||
@@ -336,8 +338,8 @@ void TriggerStore::AddTrigger(std::string name, const std::string &query,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||
TriggerEventType event_type, TriggerPhase phase,
|
||||
utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
|
||||
utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
|
||||
std::optional<std::string> owner, const query::AuthChecker *auth_checker) {
|
||||
const InterpreterConfig::Query &query_config, std::optional<std::string> owner,
|
||||
const query::AuthChecker *auth_checker) {
|
||||
std::unique_lock store_guard{store_lock_};
|
||||
if (storage_.Get(name)) {
|
||||
throw utils::BasicException("Trigger with the same name already exists.");
|
||||
@@ -345,8 +347,8 @@ void TriggerStore::AddTrigger(std::string name, const std::string &query,
|
||||
|
||||
std::optional<Trigger> trigger;
|
||||
try {
|
||||
trigger.emplace(std::move(name), query, user_parameters, event_type, query_cache, db_accessor, antlr_lock,
|
||||
query_config, std::move(owner), auth_checker);
|
||||
trigger.emplace(std::move(name), query, user_parameters, event_type, query_cache, db_accessor, query_config,
|
||||
std::move(owner), auth_checker);
|
||||
} catch (const utils::BasicException &e) {
|
||||
const auto identifiers = GetPredefinedIdentifiers(event_type);
|
||||
std::stringstream identifier_names_stream;
|
||||
|
||||
@@ -34,13 +34,13 @@ namespace memgraph::query {
|
||||
struct Trigger {
|
||||
explicit Trigger(std::string name, const std::string &query,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters, TriggerEventType event_type,
|
||||
utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor, utils::SpinLock *antlr_lock,
|
||||
utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
|
||||
const InterpreterConfig::Query &query_config, std::optional<std::string> owner,
|
||||
const query::AuthChecker *auth_checker);
|
||||
|
||||
void Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution_memory, double max_execution_time_sec,
|
||||
std::atomic<bool> *is_shutting_down, const TriggerContext &context,
|
||||
const AuthChecker *auth_checker) const;
|
||||
std::atomic<bool> *is_shutting_down, const TriggerContext &context, memgraph::auth::User *user,
|
||||
AuthChecker *auth_checker) const;
|
||||
|
||||
bool operator==(const Trigger &other) const { return name_ == other.name_; }
|
||||
// NOLINTNEXTLINE (modernize-use-nullptr)
|
||||
@@ -81,14 +81,13 @@ struct TriggerStore {
|
||||
explicit TriggerStore(std::filesystem::path directory);
|
||||
|
||||
void RestoreTriggers(utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
|
||||
utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
|
||||
const query::AuthChecker *auth_checker);
|
||||
const InterpreterConfig::Query &query_config, const query::AuthChecker *auth_checker);
|
||||
|
||||
void AddTrigger(std::string name, const std::string &query,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters, TriggerEventType event_type,
|
||||
TriggerPhase phase, utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
|
||||
utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
|
||||
std::optional<std::string> owner, const query::AuthChecker *auth_checker);
|
||||
const InterpreterConfig::Query &query_config, std::optional<std::string> owner,
|
||||
const query::AuthChecker *auth_checker);
|
||||
|
||||
void DropTrigger(const std::string &name);
|
||||
|
||||
|
||||
@@ -214,9 +214,6 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) :
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
return;
|
||||
case Type::Graph:
|
||||
new (&graph_v) Graph(other.graph_v, memory_);
|
||||
return;
|
||||
}
|
||||
LOG_FATAL("Unsupported TypedValue::Type");
|
||||
}
|
||||
@@ -266,8 +263,6 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
break;
|
||||
case Type::Graph:
|
||||
new (&graph_v) Graph(std::move(other.graph_v), memory_);
|
||||
}
|
||||
other.DestroyValue();
|
||||
}
|
||||
@@ -302,8 +297,6 @@ TypedValue::operator storage::PropertyValue() const {
|
||||
storage::TemporalData{storage::TemporalType::LocalDateTime, local_date_time_v.MicrosecondsSinceEpoch()});
|
||||
case Type::Duration:
|
||||
return storage::PropertyValue(storage::TemporalData{storage::TemporalType::Duration, duration_v.microseconds});
|
||||
case Type::Graph:
|
||||
throw TypedValueException("Unsupported copy from StorageValue to TypedValue");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -338,7 +331,6 @@ DEFINE_VALUE_AND_TYPE_GETTERS(utils::Date, Date, date_v)
|
||||
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime, local_time_v)
|
||||
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime, local_date_time_v)
|
||||
DEFINE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration, duration_v)
|
||||
DEFINE_VALUE_AND_TYPE_GETTERS(Graph, Graph, graph_v)
|
||||
|
||||
#undef DEFINE_VALUE_AND_TYPE_GETTERS
|
||||
|
||||
@@ -395,8 +387,6 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
|
||||
return os << "local_date_time";
|
||||
case TypedValue::Type::Duration:
|
||||
return os << "duration";
|
||||
case TypedValue::Type::Graph:
|
||||
return os << "graph";
|
||||
}
|
||||
LOG_FATAL("Unsupported TypedValue::Type");
|
||||
}
|
||||
@@ -532,9 +522,6 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
|
||||
case TypedValue::Type::Path:
|
||||
new (&path_v) Path(other.path_v, memory_);
|
||||
return *this;
|
||||
case TypedValue::Type::Graph:
|
||||
new (&graph_v) Graph(other.graph_v, memory_);
|
||||
return *this;
|
||||
case Type::Date:
|
||||
new (&date_v) utils::Date(other.date_v);
|
||||
return *this;
|
||||
@@ -606,9 +593,6 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
|
||||
case Type::Duration:
|
||||
new (&duration_v) utils::Duration(other.duration_v);
|
||||
break;
|
||||
case Type::Graph:
|
||||
new (&graph_v) Graph(std::move(other.graph_v), memory_);
|
||||
break;
|
||||
}
|
||||
other.DestroyValue();
|
||||
}
|
||||
@@ -649,9 +633,6 @@ void TypedValue::DestroyValue() {
|
||||
case Type::LocalDateTime:
|
||||
case Type::Duration:
|
||||
break;
|
||||
case Type::Graph:
|
||||
graph_v.~Graph();
|
||||
break;
|
||||
}
|
||||
|
||||
type_ = TypedValue::Type::Null;
|
||||
@@ -811,8 +792,6 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) {
|
||||
return TypedValue(a.ValueLocalDateTime() == b.ValueLocalDateTime(), a.GetMemoryResource());
|
||||
case TypedValue::Type::Duration:
|
||||
return TypedValue(a.ValueDuration() == b.ValueDuration(), a.GetMemoryResource());
|
||||
case TypedValue::Type::Graph:
|
||||
throw TypedValueException("Unsupported comparison operator");
|
||||
default:
|
||||
LOG_FATAL("Unhandled comparison for types");
|
||||
}
|
||||
@@ -1121,8 +1100,6 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
|
||||
case TypedValue::Type::Duration:
|
||||
return utils::DurationHash{}(value.ValueDuration());
|
||||
break;
|
||||
case TypedValue::Type::Graph:
|
||||
throw TypedValueException("Unsupported hash function for Graph");
|
||||
}
|
||||
LOG_FATAL("Unhandled TypedValue.type() in hash function");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/graph.hpp"
|
||||
#include "query/path.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
@@ -83,8 +82,7 @@ class TypedValue {
|
||||
Date,
|
||||
LocalTime,
|
||||
LocalDateTime,
|
||||
Duration,
|
||||
Graph
|
||||
Duration
|
||||
};
|
||||
|
||||
// TypedValue at this exact moment of compilation is an incomplete type, and
|
||||
@@ -403,22 +401,6 @@ class TypedValue {
|
||||
new (&path_v) Path(std::move(path), memory_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with the value of graph.
|
||||
* utils::MemoryResource is obtained from graph. After the move, graph will be
|
||||
* left empty.
|
||||
*/
|
||||
explicit TypedValue(Graph &&graph) noexcept : TypedValue(std::move(graph), graph.GetMemoryResource()) {}
|
||||
|
||||
/**
|
||||
* Construct with the value of graph and use the given MemoryResource.
|
||||
* If `*graph.GetMemoryResource() != *memory`, this call will perform an
|
||||
* element-wise move and graph is not guaranteed to be empty.
|
||||
*/
|
||||
TypedValue(Graph &&graph, utils::MemoryResource *memory) : memory_(memory), type_(Type::Graph) {
|
||||
new (&graph_v) Graph(std::move(graph), memory_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with the value of other.
|
||||
* Default utils::NewDeleteResource() is used for allocations. After the move,
|
||||
@@ -504,7 +486,6 @@ class TypedValue {
|
||||
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime)
|
||||
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime)
|
||||
DECLARE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration)
|
||||
DECLARE_VALUE_AND_TYPE_GETTERS(Graph, Graph)
|
||||
|
||||
#undef DECLARE_VALUE_AND_TYPE_GETTERS
|
||||
|
||||
@@ -547,7 +528,6 @@ class TypedValue {
|
||||
utils::LocalTime local_time_v;
|
||||
utils::LocalDateTime local_date_time_v;
|
||||
utils::Duration duration_v;
|
||||
Graph graph_v;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,5 @@ endfunction()
|
||||
copy_write_procedures_e2e_python_files(common.py)
|
||||
copy_write_procedures_e2e_python_files(conftest.py)
|
||||
copy_write_procedures_e2e_python_files(simple_write.py)
|
||||
copy_write_procedures_e2e_python_files(read_subgraph.py)
|
||||
|
||||
add_subdirectory(procedures)
|
||||
|
||||
@@ -13,64 +13,11 @@ import mgp
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(mutable=bool):
|
||||
def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
|
||||
object: mgp.Any) -> mgp.Record(mutable=bool):
|
||||
return mgp.Record(mutable=object.underlying_graph_is_mutable())
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
|
||||
return mgp.Record(mutable=ctx.graph.is_mutable())
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex):
|
||||
return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_out_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
|
||||
return [mgp.Record(edge=edge) for edge in vertex.out_edges]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_in_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
|
||||
return [mgp.Record(edge=edge) for edge in vertex.in_edges]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_2_hop_edges(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
|
||||
out_edges = vertex.out_edges
|
||||
records = []
|
||||
for edge in out_edges:
|
||||
vertex = edge.to_vertex
|
||||
properties = vertex.properties
|
||||
print(properties)
|
||||
records.extend([mgp.Record(edge=edge) for edge in edge.to_vertex.out_edges])
|
||||
return records
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_out_edges_vertex_id(ctx: mgp.ProcCtx, vertex: mgp.Vertex) -> mgp.Record(edge=mgp.Edge):
|
||||
vertex = ctx.graph.get_vertex_by_id(vertex.id)
|
||||
return [mgp.Record(edge=edge) for edge in vertex.out_edges]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_path_vertices(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex):
|
||||
return [mgp.Record(node=node) for node in path.vertices]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_path_edges(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(edge=mgp.Edge):
|
||||
return [mgp.Record(edge=edge) for edge in path.edges]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def subgraph_get_path_vertices_in_subgraph(ctx: mgp.ProcCtx, path: mgp.Path) -> mgp.Record(node=mgp.Vertex):
|
||||
path_vertices = path.vertices
|
||||
graph_vertices = ctx.graph.vertices
|
||||
records = []
|
||||
for path_vertex in path_vertices:
|
||||
if path_vertex in graph_vertices:
|
||||
records.append(mgp.Record(node=path_vertex))
|
||||
return records
|
||||
|
||||
@@ -35,12 +35,13 @@ def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def create_edge(
|
||||
ctx: mgp.ProcCtx, from_vertex: mgp.Vertex, to_vertex: mgp.Vertex, edge_type: str
|
||||
) -> mgp.Record(e=mgp.Any):
|
||||
def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex,
|
||||
to_vertex: mgp.Vertex,
|
||||
edge_type: str) -> mgp.Record(e=mgp.Any):
|
||||
e = None
|
||||
try:
|
||||
e = ctx.graph.create_edge(from_vertex, to_vertex, mgp.EdgeType(edge_type))
|
||||
e = ctx.graph.create_edge(
|
||||
from_vertex, to_vertex, mgp.EdgeType(edge_type))
|
||||
except RuntimeError as ex:
|
||||
return mgp.Record(e=str(ex))
|
||||
return mgp.Record(e=e)
|
||||
@@ -53,59 +54,32 @@ def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record():
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def set_property(ctx: mgp.ProcCtx, object: mgp.Any, name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record():
|
||||
def set_property(ctx: mgp.ProcCtx, object: mgp.Any,
|
||||
name: str, value: mgp.Nullable[mgp.Any]) -> mgp.Record():
|
||||
object.properties.set(name, value)
|
||||
return mgp.Record()
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def add_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any):
|
||||
def add_label(ctx: mgp.ProcCtx, object: mgp.Any,
|
||||
name: str) -> mgp.Record(o=mgp.Any):
|
||||
object.add_label(name)
|
||||
return mgp.Record(o=object)
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any, name: str) -> mgp.Record(o=mgp.Any):
|
||||
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any,
|
||||
name: str) -> mgp.Record(o=mgp.Any):
|
||||
object.remove_label(name)
|
||||
return mgp.Record(o=object)
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(mutable=bool):
|
||||
def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
|
||||
object: mgp.Any) -> mgp.Record(mutable=bool):
|
||||
return mgp.Record(mutable=object.underlying_graph_is_mutable())
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
|
||||
return mgp.Record(mutable=ctx.graph.is_mutable())
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def subgraph_insert_vertex_get_vertices(ctx: mgp.ProcCtx) -> mgp.Record(node=mgp.Vertex):
|
||||
ctx.graph.create_vertex()
|
||||
return [mgp.Record(node=node) for node in ctx.graph.vertices]
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def subgraph_insert_edge_get_vertex_out_edges(
|
||||
ctx: mgp.ProcCtx, vertex1: mgp.Vertex, vertex2: mgp.Vertex
|
||||
) -> mgp.Record(edge=mgp.Edge):
|
||||
ctx.graph.create_edge(vertex1, vertex2, edge_type=mgp.EdgeType("EDGE_TYPE"))
|
||||
return [mgp.Record(edge=edge) for edge in vertex1.out_edges]
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def subgraph_remove_edge_get_vertex_out_edges(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record(edge=mgp.Edge):
|
||||
from_vertex = edge.from_vertex
|
||||
ctx.graph.delete_edge(edge)
|
||||
return [mgp.Record(edge=edge) for edge in from_vertex.out_edges]
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def subgraph_remove_vertex_and_out_edges_get_vertices(
|
||||
ctx: mgp.ProcCtx, vertex: mgp.Vertex
|
||||
) -> mgp.Record(node=mgp.Vertex):
|
||||
out_edges = vertex.out_edges
|
||||
for edge in out_edges:
|
||||
ctx.graph.delete_edge(edge)
|
||||
return [mgp.Record(node=vertex) for vertex in ctx.graph.vertices]
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
# Copyright 2022 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.
|
||||
|
||||
import typing
|
||||
import mgclient
|
||||
import sys
|
||||
import pytest
|
||||
from common import execute_and_fetch_all, has_n_result_row
|
||||
|
||||
|
||||
def create_subgraph(cursor):
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 3});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 4});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 1}) MATCH (p2:Person {id:2}) CREATE (p1)-[:KNOWS]->(p2);")
|
||||
execute_and_fetch_all(cursor, "MATCH (t1:Team {id: 5}) MATCH (t2:Team {id:6}) CREATE (t1)-[:IS_RIVAL_TO]->(t2);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p1:Person {id: 3}) MATCH (p2:Person {id:4}) CREATE (p1)-[:KNOWS]->(p2);")
|
||||
|
||||
|
||||
def create_smaller_subgraph(cursor):
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 1});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Person {id: 2});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 5});")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Team {id: 6});")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:5}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 1}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
execute_and_fetch_all(cursor, "MATCH (p:Person {id: 2}) MATCH (t:Team {id:6}) CREATE (p)-[:SUPPORTS]->(t);")
|
||||
|
||||
|
||||
def test_get_vertices(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph CALL read.subgraph_get_vertices(graph) YIELD node RETURN node;",
|
||||
4,
|
||||
)
|
||||
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_get_out_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges(graph, n1) YIELD edge RETURN edge;",
|
||||
2,
|
||||
)
|
||||
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_get_in_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (t1:Team {{id:6}}) CALL read.subgraph_get_in_edges(graph, t1) YIELD edge RETURN edge;",
|
||||
2,
|
||||
)
|
||||
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_get_2_hop_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_2_hop_edges(graph, n1) YIELD edge RETURN edge;",
|
||||
0,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_get_out_edges_vertex_id(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor=cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) AS graph MATCH (n1:Person {{id:1}}) CALL read.subgraph_get_out_edges_vertex_id(graph, n1) YIELD edge RETURN edge;",
|
||||
2,
|
||||
)
|
||||
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_get_path_vertices(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(a:Person {{id: 1}})-[:SUPPORTS]->(b:Team {{id:5}}) CALL read.subgraph_get_path_vertices(graph, path) YIELD node RETURN node;",
|
||||
2,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_get_path_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_edges(graph, path) YIELD edge RETURN edge;",
|
||||
1,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_get_path_vertices_in_subgraph(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH path=(:Person {{id: 1}})-[:SUPPORTS]->(:Team {{id:5}}) CALL read.subgraph_get_path_vertices_in_subgraph(graph, path) YIELD node RETURN node;",
|
||||
2,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_insert_vertex_get_vertices(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph CALL write.subgraph_insert_vertex_get_vertices(graph) YIELD node RETURN node;",
|
||||
5,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_insert_edge_get_vertex_out_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:2}}) MATCH (t1:Team {{id:6}}) CALL write.subgraph_insert_edge_get_vertex_out_edges(graph, p1, t1) YIELD edge RETURN edge;",
|
||||
2,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_remove_edge_get_vertex_out_edges(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_subgraph(cursor)
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 6)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}})-[e:SUPPORTS]->(t1:Team {{id:5}}) CALL write.subgraph_remove_edge_get_vertex_out_edges(graph, e) YIELD edge RETURN edge;",
|
||||
1,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
def test_subgraph_remove_vertex_and_out_edges_get_vertices(connection):
|
||||
cursor = connection.cursor()
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
create_smaller_subgraph(cursor)
|
||||
assert has_n_result_row(cursor, "MATCH (n) RETURN n;", 4)
|
||||
assert has_n_result_row(
|
||||
cursor,
|
||||
f"MATCH p=(n:Person)-[:SUPPORTS]->(m:Team) WITH project(p) as graph MATCH (p1:Person {{id:1}}) CALL write.subgraph_remove_vertex_and_out_edges_get_vertices(graph, p1) YIELD node RETURN node;",
|
||||
3,
|
||||
)
|
||||
execute_and_fetch_all(
|
||||
cursor,
|
||||
f"MATCH (n) DETACH DELETE n;",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -73,6 +73,16 @@ Feature: Aggregations
|
||||
| n |
|
||||
| 5 |
|
||||
|
||||
Scenario: Count test 07:
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN count(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(null) |
|
||||
| 0 |
|
||||
|
||||
Scenario: Sum test 01:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
@@ -114,6 +124,16 @@ Feature: Aggregations
|
||||
| 4 | 0 |
|
||||
| 4 | 1 |
|
||||
|
||||
Scenario: Sum test 04:
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN sum(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| sum(null) |
|
||||
| 0 |
|
||||
|
||||
Scenario: Avg test 01:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
@@ -155,6 +175,16 @@ Feature: Aggregations
|
||||
| 2.0 | 0 |
|
||||
| 4.0 | 1 |
|
||||
|
||||
Scenario: Avg test 04:
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN avg(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| avg(null) |
|
||||
| null |
|
||||
|
||||
Scenario: Min test 01:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
@@ -196,6 +226,16 @@ Feature: Aggregations
|
||||
| 1 | 0 |
|
||||
| 4 | 1 |
|
||||
|
||||
Scenario: Min test 04:
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN min(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| min(null) |
|
||||
| null |
|
||||
|
||||
Scenario: Max test 01:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
@@ -237,6 +277,16 @@ Feature: Aggregations
|
||||
| 3 | 0 |
|
||||
| 4 | 1 |
|
||||
|
||||
Scenario: Max test 04:
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
"""
|
||||
RETURN max(null)
|
||||
"""
|
||||
Then the result should be:
|
||||
| max(null) |
|
||||
| null |
|
||||
|
||||
Scenario: Collect test 01:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
@@ -279,3 +329,18 @@ Feature: Aggregations
|
||||
| n |
|
||||
| {a_key: 13, b_key: 11, c_key: 12} |
|
||||
|
||||
Scenario: Combined aggregations - some evauluates to null:
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (f)
|
||||
CREATE (n {property: 1})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n) RETURN count(n) < n.property, count(n.property), count(n), avg(n.property), min(n.property), max(n.property), sum(n.property)
|
||||
"""
|
||||
Then the result should be:
|
||||
| count(n) < n.property | count(n.property) | count(n) | avg(n.property) | min(n.property) | max(n.property) | sum(n.property) |
|
||||
| false | 1 | 1 | 1.0 | 1 | 1 | 1 |
|
||||
| null | 0 | 1 | null | null | null | 0 |
|
||||
|
||||
@@ -10,6 +10,9 @@ add_subdirectory(transactions)
|
||||
# auth test binaries
|
||||
add_subdirectory(auth)
|
||||
|
||||
# lba test binaries
|
||||
add_subdirectory(lba)
|
||||
|
||||
## distributed ha/basic binaries
|
||||
#add_subdirectory(ha/basic)
|
||||
#
|
||||
|
||||
13
tests/integration/lba/CMakeLists.txt
Normal file
13
tests/integration/lba/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
set(target_name memgraph__integration__lba)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
set(filtering_target_name ${target_name}__filtering)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-communication)
|
||||
|
||||
add_executable(${filtering_target_name} filtering.cpp)
|
||||
set_target_properties(${filtering_target_name} PROPERTIES OUTPUT_NAME filtering)
|
||||
target_link_libraries(${filtering_target_name} mg-communication)
|
||||
59
tests/integration/lba/filtering.cpp
Normal file
59
tests/integration/lba/filtering.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2022 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 <gflags/gflags.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "admin", "Username for the database");
|
||||
DEFINE_string(password, "admin", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
/**
|
||||
* Verifies that user 'user' has privileges that are given as positional
|
||||
* arguments.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(&context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
try {
|
||||
std::string query(argv[1]);
|
||||
auto ret = client.Execute(query, {});
|
||||
uint64_t count_got = ret.records.size();
|
||||
|
||||
if (count_got != std::atoi(argv[2])) {
|
||||
LOG_FATAL("Expected the record to have {} entries but they had {} entries!", argv[2], count_got);
|
||||
}
|
||||
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}', {}",
|
||||
e.what(), argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
129
tests/integration/lba/runner.py
Normal file
129
tests/integration/lba/runner.py
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
# Copyright 2021 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.
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
UNAUTHORIZED_ERROR = "You are not authorized to execute this query! Please " "contact your database administrator."
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(
|
||||
binary, queries, should_fail=False, failure_message="", username="", password="", check_failure=True
|
||||
):
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
if should_fail:
|
||||
args.append("--should-fail")
|
||||
if failure_message:
|
||||
args.extend(["--failure-message", failure_message])
|
||||
if check_failure:
|
||||
args.append("--check-failure")
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_filtering(binary: str, queries: List[str], expected: int, username: str = "", password: str = "") -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
|
||||
args.extend(queries)
|
||||
args.append(str(expected))
|
||||
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_test(memgraph_binary: str, tester_binary: str, filtering_binary: str) -> None:
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
|
||||
|
||||
def execute_admin_queries(queries):
|
||||
return execute_tester(
|
||||
tester_binary, queries, should_fail=False, check_failure=True, username="admin", password="admin"
|
||||
)
|
||||
|
||||
def execute_user_queries(queries, should_fail=False, failure_message="", check_failure=True):
|
||||
return execute_tester(tester_binary, queries, should_fail, failure_message, "user", "user", check_failure)
|
||||
|
||||
# Start the memgraph binary
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
def cleanup():
|
||||
if memgraph.poll() is None:
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
# Prepare all users
|
||||
execute_admin_queries(
|
||||
[
|
||||
"CREATE USER admin IDENTIFIED BY 'admin'",
|
||||
"GRANT ALL PRIVILEGES TO admin",
|
||||
"CREATE USER user IDENTIFIED BY 'user'",
|
||||
"GRANT LABELS :label1, :label2, :label3 TO user",
|
||||
"GRANT EDGE_TYPES :edgeType1, :edgeType2 TO user",
|
||||
"MERGE (l1:label1 {name: 'test1'})",
|
||||
"MERGE (l2:label2 {name: 'test2'})",
|
||||
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2)",
|
||||
"MERGE (l3:label3 {name: 'test3'})",
|
||||
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3)",
|
||||
]
|
||||
)
|
||||
|
||||
# Run the test with all combinations of permissions
|
||||
print("\033[1;36m~~ Starting edge filtering test ~~\033[0m")
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 2, "user", "user")
|
||||
execute_admin_queries(["DENY EDGE_TYPES :edgeType1 TO user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 1, "user", "user")
|
||||
execute_admin_queries(["GRANT EDGE_TYPES :edgeType1 TO user", "DENY LABELS :label3 TO user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 1, "user", "user")
|
||||
execute_admin_queries(["REVOKE LABELS * FROM user", "REVOKE EDGE_TYPES * FROM user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
|
||||
print("\033[1;36m~~ Finished edge filtering test ~~\033[0m\n")
|
||||
|
||||
# Shutdown the memgraph binary
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
|
||||
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "tester")
|
||||
filtering_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "filtering")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--tester", default=tester_binary)
|
||||
parser.add_argument("--filtering", default=filtering_binary)
|
||||
args = parser.parse_args()
|
||||
|
||||
execute_test(args.memgraph, args.tester, args.filtering)
|
||||
|
||||
sys.exit(0)
|
||||
84
tests/integration/lba/tester.cpp
Normal file
84
tests/integration/lba/tester.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2022 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 <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
|
||||
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
|
||||
DEFINE_string(failure_message, "", "Set to the expected failure message.");
|
||||
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(&context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string query(argv[i]);
|
||||
try {
|
||||
client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
if (!FLAGS_check_failure) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have succeeded or failed with an error "
|
||||
"message that isn't equal to '{}' but it failed with that error "
|
||||
"message",
|
||||
FLAGS_failure_message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (FLAGS_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() != FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, e.what());
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
if (!FLAGS_check_failure) continue;
|
||||
if (FLAGS_should_fail) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed but instead it executed "
|
||||
"successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -20,6 +20,15 @@
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/stacktrace.hpp"
|
||||
|
||||
// This test was introduced because Antlr Cpp runtime doesn't work well in a
|
||||
// highly concurrent environment. Interpreter `interpret.hpp` contains
|
||||
// `antlr_lock` used to avoid crashes.
|
||||
// v4.6 and before -> Crashes.
|
||||
// v4.8 -> Does NOT crash but sometimes this tests does NOT finish.
|
||||
// Looks like a deadlock. -> The lock is still REQUIRED.
|
||||
// v4.9 -> Seems to be working.
|
||||
// v4.10 -> Seems to be working as well. -> antlr_lock removed
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST(Antlr, Sigsegv) {
|
||||
|
||||
@@ -17,3 +17,6 @@ endfunction(add_stress_test)
|
||||
|
||||
add_stress_test(long_running.cpp)
|
||||
target_link_libraries(${test_prefix}long_running mg-communication mg-io mg-utils)
|
||||
|
||||
add_stress_test(parser.cpp)
|
||||
target_link_libraries(${test_prefix}parser mg-communication mg-io mg-utils mgclient)
|
||||
|
||||
@@ -26,6 +26,11 @@ SMALL_DATASET = [
|
||||
"options": ["--vertex-count", "40000", "--create-pack-size", "100"],
|
||||
"timeout": 5,
|
||||
},
|
||||
{
|
||||
"test": "parser.cpp",
|
||||
"options": ["--per-worker-query-count", "1000"],
|
||||
"timeout": 5,
|
||||
},
|
||||
{
|
||||
"test": "long_running.cpp",
|
||||
"options": ["--vertex-count", "1000", "--edge-count", "5000", "--max-time", "1", "--verify", "20"],
|
||||
@@ -42,30 +47,35 @@ SMALL_DATASET = [
|
||||
# bipartite.py and create_match.py run for approx. 15min
|
||||
# long_running runs for 5min x 6 times = 30min
|
||||
# long_running runs for 8h
|
||||
LARGE_DATASET = [
|
||||
{
|
||||
"test": "bipartite.py",
|
||||
"options": ["--u-count", "300", "--v-count", "300"],
|
||||
"timeout": 30,
|
||||
},
|
||||
{
|
||||
"test": "create_match.py",
|
||||
"options": ["--vertex-count", "500000", "--create-pack-size", "500"],
|
||||
"timeout": 30,
|
||||
},
|
||||
] + [
|
||||
{
|
||||
"test": "long_running.cpp",
|
||||
"options": ["--vertex-count", "10000", "--edge-count", "40000", "--max-time", "5", "--verify", "60"],
|
||||
"timeout": 16,
|
||||
},
|
||||
] * 6 + [
|
||||
{
|
||||
"test": "long_running.cpp",
|
||||
"options": ["--vertex-count", "200000", "--edge-count", "1000000", "--max-time", "480", "--verify", "300"],
|
||||
"timeout": 500,
|
||||
},
|
||||
]
|
||||
LARGE_DATASET = (
|
||||
[
|
||||
{
|
||||
"test": "bipartite.py",
|
||||
"options": ["--u-count", "300", "--v-count", "300"],
|
||||
"timeout": 30,
|
||||
},
|
||||
{
|
||||
"test": "create_match.py",
|
||||
"options": ["--vertex-count", "500000", "--create-pack-size", "500"],
|
||||
"timeout": 30,
|
||||
},
|
||||
]
|
||||
+ [
|
||||
{
|
||||
"test": "long_running.cpp",
|
||||
"options": ["--vertex-count", "10000", "--edge-count", "40000", "--max-time", "5", "--verify", "60"],
|
||||
"timeout": 16,
|
||||
},
|
||||
]
|
||||
* 6
|
||||
+ [
|
||||
{
|
||||
"test": "long_running.cpp",
|
||||
"options": ["--vertex-count", "200000", "--edge-count", "1000000", "--max-time", "480", "--verify", "300"],
|
||||
"timeout": 500,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
# paths
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -101,8 +111,7 @@ def run_test(args, test, options, timeout):
|
||||
# find binary
|
||||
if test.endswith(".py"):
|
||||
logging = "DEBUG" if args.verbose else "WARNING"
|
||||
binary = [args.python, "-u", os.path.join(SCRIPT_DIR, test),
|
||||
"--logging", logging]
|
||||
binary = [args.python, "-u", os.path.join(SCRIPT_DIR, test), "--logging", logging]
|
||||
elif test.endswith(".cpp"):
|
||||
exe = os.path.join(BUILD_DIR, "tests", "stress", test[:-4])
|
||||
binary = [exe]
|
||||
@@ -112,11 +121,10 @@ def run_test(args, test, options, timeout):
|
||||
# start test
|
||||
cmd = binary + ["--worker-count", str(THREADS)] + options
|
||||
start = time.time()
|
||||
ret_test = subprocess.run(cmd, cwd = SCRIPT_DIR, timeout = timeout * 60)
|
||||
ret_test = subprocess.run(cmd, cwd=SCRIPT_DIR, timeout=timeout * 60)
|
||||
|
||||
if ret_test.returncode != 0:
|
||||
raise Exception("Test '{}' binary returned non-zero ({})!".format(
|
||||
test, ret_test.returncode))
|
||||
raise Exception("Test '{}' binary returned non-zero ({})!".format(test, ret_test.returncode))
|
||||
|
||||
runtime = time.time() - start
|
||||
print(" Done after {:.3f} seconds".format(runtime))
|
||||
@@ -125,39 +133,54 @@ def run_test(args, test, options, timeout):
|
||||
|
||||
|
||||
# parse arguments
|
||||
parser = argparse.ArgumentParser(description = "Run stress tests on Memgraph.")
|
||||
parser.add_argument("--memgraph", default = os.path.join(BUILD_DIR,
|
||||
"memgraph"))
|
||||
parser.add_argument("--log-file", default = "")
|
||||
parser.add_argument("--data-directory", default = "")
|
||||
parser.add_argument("--python", default = os.path.join(SCRIPT_DIR,
|
||||
"ve3", "bin", "python3"), type = str)
|
||||
parser.add_argument("--large-dataset", action = "store_const",
|
||||
const = True, default = False)
|
||||
parser.add_argument("--use-ssl", action = "store_const",
|
||||
const = True, default = False)
|
||||
parser.add_argument("--verbose", action = "store_const",
|
||||
const = True, default = False)
|
||||
parser = argparse.ArgumentParser(description="Run stress tests on Memgraph.")
|
||||
parser.add_argument("--memgraph", default=os.path.join(BUILD_DIR, "memgraph"))
|
||||
parser.add_argument("--log-file", default="")
|
||||
parser.add_argument("--data-directory", default="")
|
||||
parser.add_argument("--python", default=os.path.join(SCRIPT_DIR, "ve3", "bin", "python3"), type=str)
|
||||
parser.add_argument("--large-dataset", action="store_const", const=True, default=False)
|
||||
parser.add_argument("--use-ssl", action="store_const", const=True, default=False)
|
||||
parser.add_argument("--verbose", action="store_const", const=True, default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
# generate temporary SSL certs
|
||||
if args.use_ssl:
|
||||
# https://unix.stackexchange.com/questions/104171/create-ssl-certificate-non-interactively
|
||||
subj = "/C=HR/ST=Zagreb/L=Zagreb/O=Memgraph/CN=db.memgraph.com"
|
||||
subprocess.run(["openssl", "req", "-new", "-newkey", "rsa:4096",
|
||||
"-days", "365", "-nodes", "-x509", "-subj", subj,
|
||||
"-keyout", KEY_FILE, "-out", CERT_FILE], check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"openssl",
|
||||
"req",
|
||||
"-new",
|
||||
"-newkey",
|
||||
"rsa:4096",
|
||||
"-days",
|
||||
"365",
|
||||
"-nodes",
|
||||
"-x509",
|
||||
"-subj",
|
||||
subj,
|
||||
"-keyout",
|
||||
KEY_FILE,
|
||||
"-out",
|
||||
CERT_FILE,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
# start memgraph
|
||||
cwd = os.path.dirname(args.memgraph)
|
||||
cmd = [args.memgraph, "--bolt-num-workers=" + str(THREADS),
|
||||
"--storage-properties-on-edges=true",
|
||||
"--storage-snapshot-on-exit=true",
|
||||
"--storage-snapshot-interval-sec=600",
|
||||
"--storage-snapshot-retention-count=1",
|
||||
"--storage-wal-enabled=true",
|
||||
"--storage-recover-on-startup=false",
|
||||
"--query-execution-timeout-sec=1200"]
|
||||
cmd = [
|
||||
args.memgraph,
|
||||
"--bolt-num-workers=" + str(THREADS),
|
||||
"--storage-properties-on-edges=true",
|
||||
"--storage-snapshot-on-exit=true",
|
||||
"--storage-snapshot-interval-sec=600",
|
||||
"--storage-snapshot-retention-count=1",
|
||||
"--storage-wal-enabled=true",
|
||||
"--storage-recover-on-startup=false",
|
||||
"--query-execution-timeout-sec=1200",
|
||||
]
|
||||
if not args.verbose:
|
||||
cmd += ["--log-level", "WARNING"]
|
||||
if args.log_file:
|
||||
@@ -166,7 +189,7 @@ if args.data_directory:
|
||||
cmd += ["--data-directory", args.data_directory]
|
||||
if args.use_ssl:
|
||||
cmd += ["--bolt-cert-file", CERT_FILE, "--bolt-key-file", KEY_FILE]
|
||||
proc_mg = subprocess.Popen(cmd, cwd = cwd)
|
||||
proc_mg = subprocess.Popen(cmd, cwd=cwd)
|
||||
wait_for_server(7687)
|
||||
assert proc_mg.poll() is None, "The database binary died prematurely!"
|
||||
|
||||
@@ -174,10 +197,12 @@ assert proc_mg.poll() is None, "The database binary died prematurely!"
|
||||
@atexit.register
|
||||
def cleanup():
|
||||
global proc_mg
|
||||
if proc_mg.poll() != None: return
|
||||
if proc_mg.poll() != None:
|
||||
return
|
||||
proc_mg.kill()
|
||||
proc_mg.wait()
|
||||
|
||||
|
||||
# run tests
|
||||
runtimes = {}
|
||||
dataset = LARGE_DATASET if args.large_dataset else SMALL_DATASET
|
||||
|
||||
79
tests/stress/parser.cpp
Normal file
79
tests/stress/parser.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2022 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 <limits>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "mgclient.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
DEFINE_int32(worker_count, 1, "The number of concurrent workers executing queries against the server.");
|
||||
DEFINE_int32(per_worker_query_count, 100, "The number of queries each worker will try to execute.");
|
||||
|
||||
auto make_client() {
|
||||
mg::Client::Params params;
|
||||
params.host = FLAGS_address;
|
||||
params.port = static_cast<uint16_t>(FLAGS_port);
|
||||
params.use_ssl = FLAGS_use_ssl;
|
||||
return mg::Client::Connect(params);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
mg::Client::Init();
|
||||
|
||||
spdlog::info("Cleaning the database instance...");
|
||||
auto client = make_client();
|
||||
client->Execute("MATCH (n) DETACH DELETE n");
|
||||
client->DiscardAll();
|
||||
|
||||
spdlog::info(fmt::format("Starting parser stress test with {} workers and {} queries per worker...",
|
||||
FLAGS_worker_count, FLAGS_per_worker_query_count));
|
||||
std::vector<std::thread> threads;
|
||||
memgraph::utils::Timer timer;
|
||||
for (int i = 0; i < FLAGS_worker_count; ++i) {
|
||||
threads.push_back(std::thread([]() {
|
||||
auto client = make_client();
|
||||
std::mt19937 generator{std::random_device{}()};
|
||||
std::uniform_int_distribution<uint64_t> distribution{std::numeric_limits<uint64_t>::min(),
|
||||
std::numeric_limits<uint64_t>::max()};
|
||||
for (int i = 0; i < FLAGS_per_worker_query_count; ++i) {
|
||||
try {
|
||||
auto is_executed = client->Execute(fmt::format("MATCH (n:Label{}) RETURN n;", distribution(generator)));
|
||||
if (!is_executed) {
|
||||
LOG_FATAL("One of the parser stress test queries failed.");
|
||||
}
|
||||
client->FetchAll();
|
||||
} catch (const std::exception &e) {
|
||||
LOG_FATAL("One of the parser stress test queries failed.");
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
std::ranges::for_each(threads, [](auto &t) { t.join(); });
|
||||
spdlog::info(
|
||||
fmt::format("All queries executed in {:.4f}s. The parser managed to handle the load.", timer.Elapsed().count()));
|
||||
mg::Client::Finalize();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/crypto.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/license.hpp"
|
||||
@@ -159,6 +160,73 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
|
||||
ASSERT_FALSE(auth.HasUsers());
|
||||
ASSERT_TRUE(auth.AddUser("test"));
|
||||
ASSERT_TRUE(auth.HasUsers());
|
||||
|
||||
auto user = auth.GetUser("test");
|
||||
ASSERT_NE(user, std::nullopt);
|
||||
|
||||
// Test initial user fine grained access permissions.
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), FineGrainedAccessPermissions{});
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(), FineGrainedAccessPermissions{});
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
|
||||
user->GetFineGrainedAccessEdgeTypePermissions());
|
||||
|
||||
// Grant one label to user .
|
||||
user->fine_grained_access_handler().label_permissions().Grant("labelTest");
|
||||
// Grant one edge type to user .
|
||||
user->fine_grained_access_handler().edge_type_permissions().Grant("edgeTypeTest");
|
||||
|
||||
// Check permissions.
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest"), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest"), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
|
||||
user->GetFineGrainedAccessEdgeTypePermissions());
|
||||
|
||||
// Deny one label to user .
|
||||
user->fine_grained_access_handler().label_permissions().Deny("labelTest1");
|
||||
// Deny one edge type to user .
|
||||
user->fine_grained_access_handler().edge_type_permissions().Deny("edgeTypeTest1");
|
||||
|
||||
// Check permissions.
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest1"), PermissionLevel::DENY);
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest1"), PermissionLevel::DENY);
|
||||
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
|
||||
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
|
||||
user->GetFineGrainedAccessEdgeTypePermissions());
|
||||
|
||||
// Create role.
|
||||
ASSERT_TRUE(auth.AddRole("admin"));
|
||||
auto role = auth.GetRole("admin");
|
||||
ASSERT_NE(role, std::nullopt);
|
||||
|
||||
// Grant label and edge type to role and role to user.
|
||||
role->fine_grained_access_handler().label_permissions().Grant("roleLabelTest");
|
||||
role->fine_grained_access_handler().edge_type_permissions().Grant("roleEdgeTypeTest");
|
||||
user->SetRole(*role);
|
||||
|
||||
// Check permissions.
|
||||
{
|
||||
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest"), PermissionLevel::GRANT);
|
||||
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest"), PermissionLevel::GRANT);
|
||||
}
|
||||
|
||||
// Deny label and edge type to role and role to user.
|
||||
role->fine_grained_access_handler().label_permissions().Deny("roleLabelTest1");
|
||||
role->fine_grained_access_handler().edge_type_permissions().Deny("roleEdgeTypeTest1");
|
||||
user->SetRole(*role);
|
||||
|
||||
// Check permissions.
|
||||
{
|
||||
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest1"), PermissionLevel::DENY);
|
||||
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest1"), PermissionLevel::DENY);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AuthWithStorage, RoleManipulations) {
|
||||
{
|
||||
auto user1 = auth.AddUser("user1");
|
||||
@@ -468,8 +536,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
|
||||
}
|
||||
{
|
||||
auto perms = Permissions();
|
||||
auto user1 = User("test", "pw", perms);
|
||||
auto user2 = User("Test", "pw", perms);
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler();
|
||||
auto user1 = User("test", "pw", perms, fine_grained_access_handler);
|
||||
auto user2 = User("Test", "pw", perms, fine_grained_access_handler);
|
||||
ASSERT_EQ(user1, user2);
|
||||
ASSERT_EQ(user1.username(), user2.username());
|
||||
ASSERT_EQ(user1.username(), "test");
|
||||
@@ -485,8 +554,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
|
||||
}
|
||||
{
|
||||
auto perms = Permissions();
|
||||
auto role1 = Role("role", perms);
|
||||
auto role2 = Role("Role", perms);
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler();
|
||||
auto role1 = Role("role", perms, fine_grained_access_handler);
|
||||
auto role2 = Role("Role", perms, fine_grained_access_handler);
|
||||
ASSERT_EQ(role1, role2);
|
||||
ASSERT_EQ(role1.rolename(), role2.rolename());
|
||||
ASSERT_EQ(role1.rolename(), "role");
|
||||
|
||||
@@ -136,8 +136,6 @@ inline std::string ToString(const memgraph::query::TypedValue &value, const TAcc
|
||||
case memgraph::query::TypedValue::Type::Duration:
|
||||
os << ToString(value.ValueDuration());
|
||||
break;
|
||||
case memgraph::query::TypedValue::Type::Graph:
|
||||
throw std::logic_error{"Not implemented"};
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
@@ -531,9 +531,9 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
|
||||
memgraph::query::test_common::OnCreate { \
|
||||
std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \
|
||||
std::vector<memgraph::query::PropertyIx>{(property)})
|
||||
std::vector<memgraph::query::PropertyIx>{(property)})
|
||||
#define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__)
|
||||
#define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
|
||||
#define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
|
||||
@@ -583,7 +583,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
|
||||
#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__})
|
||||
#define EXTRACT(variable, list, expr) \
|
||||
storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr)
|
||||
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
|
||||
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges))
|
||||
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \
|
||||
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
|
||||
(labels), (edgeTypes))
|
||||
#define DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames))
|
||||
#define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__)
|
||||
|
||||
@@ -217,31 +217,40 @@ TEST_F(QueryPlanAggregateOps, WithData) {
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) {
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COUNT});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::SUM});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::AVG});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MIN});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MAX});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_LIST});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::List);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_MAP});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +269,7 @@ TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) {
|
||||
// max
|
||||
EXPECT_TRUE(results[0][3].IsNull());
|
||||
// sum
|
||||
EXPECT_TRUE(results[0][4].IsNull());
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 0);
|
||||
// avg
|
||||
EXPECT_TRUE(results[0][5].IsNull());
|
||||
// collect list
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "storage_test_utils.hpp"
|
||||
#include "test_utils.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
#define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR)
|
||||
|
||||
@@ -91,33 +90,11 @@ size_t CountMaybeIterables(TMaybeIterable &&maybe_iterable) {
|
||||
return std::distance(iterable.begin(), iterable.end());
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, const size_t number_of_edges_between) {
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
from->impl)),
|
||||
0);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
from->impl)),
|
||||
number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.InEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
to->impl)),
|
||||
number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](auto impl) { return impl.OutEdges(memgraph::storage::View::NEW); },
|
||||
},
|
||||
to->impl)),
|
||||
0);
|
||||
EXPECT_EQ(CountMaybeIterables(from->impl.InEdges(memgraph::storage::View::NEW)), 0);
|
||||
EXPECT_EQ(CountMaybeIterables(from->impl.OutEdges(memgraph::storage::View::NEW)), number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(to->impl.InEdges(memgraph::storage::View::NEW)), number_of_edges_between);
|
||||
EXPECT_EQ(CountMaybeIterables(to->impl.OutEdges(memgraph::storage::View::NEW)), 0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -98,8 +98,8 @@ TEST_F(TestPrivilegeExtractor, CreateIndex) {
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, AuthQuery) {
|
||||
auto *query =
|
||||
AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{});
|
||||
auto *query = AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{},
|
||||
std::vector<std::string>{}, std::vector<std::string>{});
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::AUTH));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "query/interpreter.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
@@ -37,6 +38,12 @@ class MockAuthChecker : public memgraph::query::AuthChecker {
|
||||
public:
|
||||
MOCK_CONST_METHOD2(IsUserAuthorized, bool(const std::optional<std::string> &username,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges));
|
||||
MOCK_CONST_METHOD3(IsUserAuthorizedEdgeType,
|
||||
bool(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const memgraph::storage::EdgeTypeId &edgeType));
|
||||
MOCK_CONST_METHOD3(IsUserAuthorizedLabels,
|
||||
bool(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
|
||||
const std::vector<memgraph::storage::LabelId> &labels));
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -891,7 +898,6 @@ class TriggerStoreTest : public ::testing::Test {
|
||||
std::optional<memgraph::query::DbAccessor> dba;
|
||||
|
||||
memgraph::utils::SkipList<memgraph::query::QueryCacheEntry> ast_cache;
|
||||
memgraph::utils::SpinLock antlr_lock;
|
||||
memgraph::query::AllowEverythingAuthChecker auth_checker;
|
||||
|
||||
private:
|
||||
@@ -909,7 +915,7 @@ TEST_F(TriggerStoreTest, Restore) {
|
||||
|
||||
const auto reset_store = [&] {
|
||||
store.emplace(testing_directory);
|
||||
store->RestoreTriggers(&ast_cache, &*dba, &antlr_lock, memgraph::query::InterpreterConfig::Query{}, &auth_checker);
|
||||
store->RestoreTriggers(&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, &auth_checker);
|
||||
};
|
||||
|
||||
reset_store();
|
||||
@@ -930,12 +936,12 @@ TEST_F(TriggerStoreTest, Restore) {
|
||||
store->AddTrigger(
|
||||
trigger_name_before, trigger_statement,
|
||||
std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{1}}},
|
||||
event_type, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
event_type, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker);
|
||||
store->AddTrigger(
|
||||
trigger_name_after, trigger_statement,
|
||||
std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{"value"}}},
|
||||
event_type, memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
event_type, memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, {owner}, &auth_checker);
|
||||
|
||||
const auto check_triggers = [&] {
|
||||
@@ -986,16 +992,16 @@ TEST_F(TriggerStoreTest, AddTrigger) {
|
||||
|
||||
// Invalid query in statements
|
||||
ASSERT_THROW(store.AddTrigger("trigger", "RETUR 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
|
||||
memgraph::utils::BasicException);
|
||||
ASSERT_THROW(store.AddTrigger("trigger", "RETURN createdEdges", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
|
||||
memgraph::utils::BasicException);
|
||||
|
||||
ASSERT_THROW(store.AddTrigger("trigger", "RETURN $parameter", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
|
||||
memgraph::utils::BasicException);
|
||||
|
||||
@@ -1003,15 +1009,15 @@ TEST_F(TriggerStoreTest, AddTrigger) {
|
||||
"trigger", "RETURN $parameter",
|
||||
std::map<std::string, memgraph::storage::PropertyValue>{{"parameter", memgraph::storage::PropertyValue{1}}},
|
||||
memgraph::query::TriggerEventType::VERTEX_CREATE, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
&antlr_lock, memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker));
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker));
|
||||
|
||||
// Inserting with the same name
|
||||
ASSERT_THROW(store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
|
||||
memgraph::utils::BasicException);
|
||||
ASSERT_THROW(store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
|
||||
memgraph::utils::BasicException);
|
||||
|
||||
@@ -1027,7 +1033,7 @@ TEST_F(TriggerStoreTest, DropTrigger) {
|
||||
|
||||
const auto *trigger_name = "trigger";
|
||||
store.AddTrigger(trigger_name, "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker);
|
||||
|
||||
ASSERT_THROW(store.DropTrigger("Unknown"), memgraph::utils::BasicException);
|
||||
@@ -1040,7 +1046,7 @@ TEST_F(TriggerStoreTest, TriggerInfo) {
|
||||
|
||||
std::vector<memgraph::query::TriggerStore::TriggerInfo> expected_info;
|
||||
store.AddTrigger("trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker);
|
||||
expected_info.push_back({"trigger", "RETURN 1", memgraph::query::TriggerEventType::VERTEX_CREATE,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT});
|
||||
@@ -1060,7 +1066,7 @@ TEST_F(TriggerStoreTest, TriggerInfo) {
|
||||
check_trigger_info();
|
||||
|
||||
store.AddTrigger("edge_update_trigger", "RETURN 1", {}, memgraph::query::TriggerEventType::EDGE_UPDATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker);
|
||||
expected_info.push_back({"edge_update_trigger", "RETURN 1", memgraph::query::TriggerEventType::EDGE_UPDATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT});
|
||||
@@ -1174,7 +1180,7 @@ TEST_F(TriggerStoreTest, AnyTriggerAllKeywords) {
|
||||
for (const auto keyword : keywords) {
|
||||
SCOPED_TRACE(keyword);
|
||||
EXPECT_NO_THROW(store.AddTrigger(trigger_name, fmt::format("RETURN {}", keyword), {}, event_type,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker));
|
||||
store.DropTrigger(trigger_name);
|
||||
}
|
||||
@@ -1199,23 +1205,23 @@ TEST_F(TriggerStoreTest, AuthCheckerUsage) {
|
||||
|
||||
ASSERT_NO_THROW(store->AddTrigger("successfull_trigger_1", "CREATE (n:VERTEX) RETURN n", {},
|
||||
memgraph::query::TriggerEventType::EDGE_UPDATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker));
|
||||
|
||||
ASSERT_NO_THROW(store->AddTrigger("successfull_trigger_2", "CREATE (n:VERTEX) RETURN n", {},
|
||||
memgraph::query::TriggerEventType::EDGE_UPDATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba,
|
||||
memgraph::query::InterpreterConfig::Query{}, owner, &mock_checker));
|
||||
|
||||
EXPECT_CALL(mock_checker, IsUserAuthorized(std::optional<std::string>{}, ElementsAre(Privilege::MATCH)))
|
||||
.Times(1)
|
||||
.WillOnce(Return(false));
|
||||
|
||||
ASSERT_THROW(store->AddTrigger("unprivileged_trigger", "MATCH (n:VERTEX) RETURN n", {},
|
||||
memgraph::query::TriggerEventType::EDGE_UPDATE,
|
||||
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
|
||||
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker);
|
||||
, memgraph::utils::BasicException);
|
||||
ASSERT_THROW(
|
||||
store->AddTrigger("unprivileged_trigger", "MATCH (n:VERTEX) RETURN n", {},
|
||||
memgraph::query::TriggerEventType::EDGE_UPDATE, memgraph::query::TriggerPhase::AFTER_COMMIT,
|
||||
&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, std::nullopt, &mock_checker);
|
||||
, memgraph::utils::BasicException);
|
||||
|
||||
store.emplace(testing_directory);
|
||||
EXPECT_CALL(mock_checker, IsUserAuthorized(std::optional<std::string>{}, ElementsAre(Privilege::CREATE)))
|
||||
@@ -1223,8 +1229,8 @@ TEST_F(TriggerStoreTest, AuthCheckerUsage) {
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(mock_checker, IsUserAuthorized(owner, ElementsAre(Privilege::CREATE))).Times(1).WillOnce(Return(true));
|
||||
|
||||
ASSERT_NO_THROW(store->RestoreTriggers(&ast_cache, &*dba, &antlr_lock, memgraph::query::InterpreterConfig::Query{},
|
||||
&mock_checker));
|
||||
ASSERT_NO_THROW(
|
||||
store->RestoreTriggers(&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, &mock_checker));
|
||||
|
||||
const auto triggers = store->GetTriggerInfo();
|
||||
ASSERT_EQ(triggers.size(), 1);
|
||||
|
||||
Reference in New Issue
Block a user