Compare commits

..

1 Commits

Author SHA1 Message Date
antoniofilipovic
b7c8dc8ae7 add files modified with hook 2022-07-15 11:02:46 +02:00
36 changed files with 485 additions and 999 deletions

View File

@@ -3,9 +3,7 @@
- [ ] 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

View File

@@ -70,11 +70,6 @@ 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.
@@ -115,22 +110,12 @@ 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 ${{ 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
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
# 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
View File

@@ -5,4 +5,3 @@
!CMakeLists.txt
!__main.cpp
!pulsar.patch
!antlr4.10.1.patch

View File

@@ -106,7 +106,6 @@ 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)

View File

@@ -1,13 +0,0 @@
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}

43
libs/antlr4.patch Normal file
View File

@@ -0,0 +1,43 @@
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;
}

View File

@@ -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.10.1-complete.jar"
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.9.2-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"]="https://www.antlr.org/download/antlr-4.10.1-complete.jar"
["antlr4-generator"]="http://www.antlr.org/download/antlr-4.9.2-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.10.1" # v4.10.1
antlr4_tag="4.9.2" # v4.9.2
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag" true
pushd antlr4
git apply ../antlr4.10.1.patch
git apply ../antlr4.patch
popd
# cppitertools v2.0 2019-12-23
@@ -199,7 +199,7 @@ git apply ../rocksdb.patch
popd
# mgclient
mgclient_tag="v1.4.0" # (2022-06-14)
mgclient_tag="96e95c6845463cbe88948392be58d26da0d5ffd3" # (2022-02-08)
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt

View File

@@ -183,137 +183,19 @@ 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);
}
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
Role::Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions)
: rolename_(utils::ToLowerCase(rolename)),
permissions_(permissions),
fine_grained_access_permissions_(fine_grained_access_permissions) {}
Role::Role(const std::string &rolename, const Permissions &permissions)
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
const std::string &Role::rolename() const { return rolename_; }
const Permissions &Role::permissions() const { return permissions_; }
Permissions &Role::permissions() { return permissions_; }
const FineGrainedAccessPermissions &Role::fine_grained_access_permissions() const {
return fine_grained_access_permissions_;
}
FineGrainedAccessPermissions &Role::fine_grained_access_permissions() { return fine_grained_access_permissions_; }
nlohmann::json Role::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["rolename"] = rolename_;
data["permissions"] = permissions_.Serialize();
data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize();
return data;
}
@@ -321,14 +203,11 @@ 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() ||
!data["fine_grained_access_permissions"].is_object()) {
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
auto fine_grained_access_permissions =
FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]);
return {data["rolename"], permissions, fine_grained_access_permissions};
return {data["rolename"], permissions};
}
bool operator==(const Role &first, const Role &second) {
@@ -337,12 +216,8 @@ bool operator==(const Role &first, const Role &second) {
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_permissions_(fine_grained_access_permissions) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
@@ -391,35 +266,10 @@ Permissions User::GetPermissions() const {
return permissions_;
}
FineGrainedAccessPermissions User::GetFineGrainedAccessPermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;
std::set_union(fine_grained_access_permissions_.grants().begin(), fine_grained_access_permissions_.grants().end(),
role_->fine_grained_access_permissions().grants().begin(),
role_->fine_grained_access_permissions().grants().end(),
std::inserter(resultGrants, resultGrants.begin()));
std::unordered_set<std::string> resultDenies;
std::set_union(fine_grained_access_permissions_.denies().begin(), fine_grained_access_permissions_.denies().end(),
role_->fine_grained_access_permissions().denies().begin(),
role_->fine_grained_access_permissions().denies().end(),
std::inserter(resultDenies, resultDenies.begin()));
return FineGrainedAccessPermissions(resultGrants, resultDenies);
}
return fine_grained_access_permissions_;
}
const std::string &User::username() const { return username_; }
const Permissions &User::permissions() const { return permissions_; }
Permissions &User::permissions() { return permissions_; }
const FineGrainedAccessPermissions &User::fine_grained_access_permissions() const {
return fine_grained_access_permissions_;
}
FineGrainedAccessPermissions &User::fine_grained_access_permissions() { return fine_grained_access_permissions_; }
const Role *User::role() const {
if (role_.has_value()) {
@@ -433,7 +283,6 @@ nlohmann::json User::Serialize() const {
data["username"] = username_;
data["password_hash"] = password_hash_;
data["permissions"] = permissions_.Serialize();
data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize();
// The role shouldn't be serialized here, it is stored as a foreign key.
return data;
}
@@ -446,9 +295,7 @@ User User::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load user data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
auto fine_grained_access_permissions =
FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_permissions};
return {data["username"], data["password_hash"], permissions};
}
bool operator==(const User &first, const User &second) {

View File

@@ -10,7 +10,6 @@
#include <optional>
#include <string>
#include <unordered_set>
#include <json/json.hpp>
@@ -89,48 +88,15 @@ 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 Role final {
public:
Role(const std::string &rolename);
Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions);
Role(const std::string &rolename, const Permissions &permissions);
const std::string &rolename() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessPermissions &fine_grained_access_permissions() const;
FineGrainedAccessPermissions &fine_grained_access_permissions();
nlohmann::json Serialize() const;
@@ -142,7 +108,6 @@ class Role final {
private:
std::string rolename_;
Permissions permissions_;
FineGrainedAccessPermissions fine_grained_access_permissions_;
};
bool operator==(const Role &first, const Role &second);
@@ -152,8 +117,7 @@ class User final {
public:
User(const std::string &username);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
/// @throw AuthException if unable to verify the password.
bool CheckPassword(const std::string &password);
@@ -166,14 +130,11 @@ class User final {
void ClearRole();
Permissions GetPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessPermissions() const;
const std::string &username() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessPermissions &fine_grained_access_permissions() const;
FineGrainedAccessPermissions &fine_grained_access_permissions();
const Role *role() const;
@@ -188,7 +149,6 @@ class User final {
std::string username_;
std::string password_hash_;
Permissions permissions_;
FineGrainedAccessPermissions fine_grained_access_permissions_;
std::optional<Role> role_;
};

View File

@@ -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,28 +751,9 @@ 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,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@@ -781,9 +762,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@@ -792,9 +772,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
void RevokePrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@@ -805,8 +784,7 @@ 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 std::vector<std::string> &labels, const TEditFun &edit_fun) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, const TEditFun &edit_fun) {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
@@ -826,18 +804,11 @@ 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_permissions(), label);
}
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_permissions(), label);
}
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
@@ -1281,8 +1252,9 @@ 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.config.query, interpreter_context.auth_checker);
interpreter_context.trigger_store.RestoreTriggers(&interpreter_context.ast_cache, &dba,
&interpreter_context.antlr_lock, 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.

View File

@@ -82,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.10.1-complete.jar
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.9.2-complete.jar
-Dlanguage=Cpp -visitor -package antlropencypher
-o ${opencypher_generated}
${opencypher_lexer_grammar} ${opencypher_parser_grammar}

View File

@@ -14,7 +14,6 @@
#include <type_traits>
#include "query/common.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/metadata.hpp"
#include "query/parameters.hpp"
@@ -73,7 +72,6 @@ struct ExecutionContext {
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
FineGrainedAccessChecker *fine_grained_access_checker{nullptr};
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@@ -21,7 +21,8 @@ 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> &params,
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config) {
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
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
@@ -62,16 +63,20 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
};
if (it == accessor.end()) {
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);
{
std::unique_lock<utils::SpinLock> guard(*antlr_lock);
// 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.");
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.");
}
}
// Convert the ANTLR4 parse tree into an AST.

View File

@@ -111,7 +111,8 @@ struct ParsedQuery {
};
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> &params,
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config);
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
const InterpreterConfig::Query &query_config);
class SingleNodeLogicalPlan final : public LogicalPlan {
public:

View File

@@ -1,24 +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 "auth/models.hpp"
#include "query/frontend/ast/ast.hpp"
#include "storage/v2/id_types.hpp"
namespace memgraph::query {
class FineGrainedAccessChecker {
public:
virtual bool IsUserAuthorizedLabels(const std::vector<memgraph::storage::LabelId> &label,
memgraph::query::DbAccessor *dba) const = 0;
};
} // namespace memgraph::query

View File

@@ -2234,7 +2234,6 @@ cpp<#
(:serialize (:slk))
(:clone))
(lcp:define-class auth-query (query)
((action "Action" :scope :public)
(user "std::string" :scope :public)
@@ -2243,8 +2242,7 @@ 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)
(labels "std::vector<std::string>" :scope :public))
(privileges "std::vector<Privilege>" :scope :public))
(:public
(lcp:define-enum action
(create-role drop-role show-roles create-user set-password drop-user
@@ -2266,14 +2264,13 @@ cpp<#
#>cpp
AuthQuery(Action action, std::string user, std::string role,
std::string user_or_role, Expression *password,
std::vector<Privilege> privileges, std::vector<std::string> labels)
std::vector<Privilege> privileges)
: action_(action),
user_(user),
role_(role),
user_or_role_(user_or_role),
password_(password),
privileges_(privileges),
labels_(labels) {}
privileges_(privileges) {}
cpp<#)
(:private
#>cpp

File diff suppressed because it is too large Load Diff

View File

@@ -115,7 +115,7 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
auto operators = ExtractOperators(all_children, allowed_operators);
for (auto *expression : _expressions) {
expressions.push_back(std::any_cast<Expression *>(expression->accept(this)));
expressions.push_back(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 = std::any_cast<Expression *>(_expression->accept(this));
Expression *expression = _expression->accept(this);
for (int i = (int)operators.size() - 1; i >= 0; --i) {
expression = CreateUnaryOperatorByToken(operators[i], expression);
}
@@ -478,11 +478,6 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
/**
* @return AuthQuery::LabelList
*/
antlrcpp::Any visitLabelList(MemgraphCypher::LabelListContext *ctx) override;
/**
* @return AuthQuery*
*/

View File

@@ -56,7 +56,6 @@ memgraphCypherKeyword : cypherKeyword
| IDENTIFIED
| ISOLATION
| KAFKA
| LABELS
| LEVEL
| LOAD
| LOCK
@@ -255,17 +254,10 @@ privilege : CREATE
| MODULE_READ
| MODULE_WRITE
| WEBSOCKET
| LABELS labels=labelList
;
privilegeList : privilege ( ',' privilege )* ;
labelList : '*' | listOfLabels ;
listOfLabels : label ( ',' label )* ;
label : COLON symbolicName ;
showPrivileges : SHOW PRIVILEGES FOR userOrRole=userOrRoleName ;
showRoleForUser : SHOW ROLE FOR user=userOrRoleName ;

View File

@@ -66,7 +66,6 @@ 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 ;

View File

@@ -204,9 +204,8 @@ const trie::Trie kKeywords = {"union",
"pulsar",
"service_url",
"version",
"websocket",
"foreach",
"labels"};
"websocket"
"foreach"};
// Unicode codepoints that are allowed at the start of the unescaped name.
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(

View File

@@ -29,7 +29,6 @@
#include "query/db_accessor.hpp"
#include "query/dump.hpp"
#include "query/exceptions.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/ast/cypher_main_visitor.hpp"
@@ -260,24 +259,6 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
private:
storage::Storage *db_;
};
class FineGrainedAccessChecker final : public memgraph::query::FineGrainedAccessChecker {
public:
explicit FineGrainedAccessChecker(memgraph::auth::User *user) : user_{user} {}
bool IsUserAuthorizedLabels(const std::vector<memgraph::storage::LabelId> &labels,
memgraph::query::DbAccessor *dba) const final {
auto labelPermissions = user_->GetFineGrainedAccessPermissions();
return std::any_of(labels.begin(), labels.end(), [&labelPermissions, &dba](const auto label) {
return labelPermissions.Has(dba->LabelToName(label)) == memgraph::auth::PermissionLevel::GRANT;
});
}
private:
memgraph::auth::User *user_;
};
/// returns false if the replication role can't be set
/// @throw QueryRuntimeException if an error ocurred.
@@ -299,7 +280,6 @@ 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> labels = auth_query->labels_;
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
Callback callback;
@@ -329,7 +309,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>>();
@@ -404,20 +384,20 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::GRANT_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges, labels] {
auth->GrantPrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges] {
auth->GrantPrivilege(user_or_role, privileges);
return std::vector<std::vector<TypedValue>>();
};
return callback;
case AuthQuery::Action::DENY_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges, labels] {
auth->DenyPrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges] {
auth->DenyPrivilege(user_or_role, privileges);
return std::vector<std::vector<TypedValue>>();
};
return callback;
case AuthQuery::Action::REVOKE_PRIVILEGE: {
callback.fn = [auth, user_or_role, privileges, labels] {
auth->RevokePrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges] {
auth->RevokePrivilege(user_or_role, privileges);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@@ -917,7 +897,7 @@ struct PullPlanVector {
struct PullPlan {
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters &parameters, bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector = nullptr,
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,
@@ -946,8 +926,7 @@ struct PullPlan {
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &parameters, const bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector,
const std::optional<size_t> memory_limit)
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),
@@ -958,12 +937,6 @@ 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()) {
memgraph::auth::User *user = interpreter_context->auth->GetUser(*username);
ctx_.fine_grained_access_checker = new FineGrainedAccessChecker{user};
}
#endif
if (interpreter_context->config.execution_timeout_sec > 0) {
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
}
@@ -1137,7 +1110,6 @@ 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);
@@ -1181,9 +1153,8 @@ 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,
StringPointerToOptional(username), trigger_context_collector, memory_limit);
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, 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> {
@@ -1210,7 +1181,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->config.query);
&interpreter_context->ast_cache, &interpreter_context->antlr_lock, 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");
@@ -1243,8 +1214,7 @@ 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,
const std::string *username) {
DbAccessor *dba, utils::MemoryResource *execution_memory) {
const std::string kProfileQueryStart = "profile ";
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
@@ -1278,7 +1248,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->config.query);
&interpreter_context->ast_cache, &interpreter_context->antlr_lock, 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");
@@ -1294,14 +1264,12 @@ 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, optional_username,
summary, dba, interpreter_context, execution_memory, memory_limit,
// 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>{},
@@ -1310,7 +1278,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, optional_username, nullptr, memory_limit)
execution_memory, nullptr, memory_limit)
.Pull(stream, {}, {}, summary);
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
}
@@ -1445,7 +1413,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, const std::string *username) {
DbAccessor *dba, utils::MemoryResource *execution_memory) {
if (in_explicit_transaction) {
throw UserModificationInMulticommandTxException();
}
@@ -1465,8 +1433,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, StringPointerToOptional(username));
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory);
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),
@@ -1598,7 +1566,8 @@ 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->config.query, std::move(owner), interpreter_context->auth_checker);
dba, &interpreter_context->antlr_lock, interpreter_context->config.query, std::move(owner),
interpreter_context->auth_checker);
return {};
}};
}
@@ -2154,8 +2123,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_->config.query);
ParsedQuery parsed_query = ParseQuery(query_string, params, &interpreter_context_->ast_cache,
&interpreter_context_->antlr_lock, interpreter_context_->config.query);
query_execution->summary["parsing_time"] = parsing_timer.Elapsed().count();
// Some queries require an active transaction in order to be prepared.
@@ -2178,7 +2147,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, username,
&query_execution->notifications,
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_,
@@ -2186,7 +2155,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, username);
&query_execution->execution_memory_with_exception);
} 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);
@@ -2196,7 +2165,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, username);
&query_execution->execution_memory_with_exception);
} 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,

View File

@@ -13,7 +13,6 @@
#include <gflags/gflags.h>
#include "auth/models.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/context.hpp"
@@ -100,19 +99,14 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 0;
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 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) = 0;
virtual void RevokePrivilege(const std::string &user_or_role,
const std::vector<AuthQuery::Privilege> &privileges) = 0;
};
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
@@ -179,6 +173,13 @@ 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};

View File

@@ -29,7 +29,6 @@
#include "query/context.hpp"
#include "query/db_accessor.hpp"
#include "query/exceptions.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/interpret/eval.hpp"
@@ -406,29 +405,11 @@ class ScanAllCursor : public Cursor {
vertices_it_.emplace(vertices_.value().begin());
}
#ifdef MG_ENTERPRISE
FilterNodes(context.fine_grained_access_checker, context.db_accessor);
if (vertices_it_.value() == vertices_.value().end()) return false;
#endif
frame[output_symbol_] = *vertices_it_.value();
++vertices_it_.value();
return true;
}
void FilterNodes(const FineGrainedAccessChecker *fine_grained_access_checker, DbAccessor *db_accessor) {
if (!fine_grained_access_checker) return;
while (vertices_it_.value() != vertices_.value().end()) {
VertexAccessor vertex = *vertices_it_.value();
auto vertex_labels = vertex.Labels(memgraph::storage::View::NEW).GetValue();
if (fine_grained_access_checker->IsUserAuthorizedLabels(vertex_labels, db_accessor)) {
break;
}
++vertices_it_.value();
}
}
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {
@@ -2619,17 +2600,18 @@ 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::COUNT:
case Aggregation::Op::SUM:
return TypedValue(0, memory);
case Aggregation::Op::COLLECT_LIST:
return TypedValue(TypedValue::TVector(memory));
case Aggregation::Op::COLLECT_MAP:
return TypedValue(TypedValue::TMap(memory));
case Aggregation::Op::PROJECT: // add here graph as aggregation value
return TypedValue(TypedValue::TVector(memory));
}
}
} // namespace
@@ -2647,7 +2629,9 @@ class AggregateCursor : public Cursor {
pulled_all_input_ = true;
aggregation_it_ = aggregation_.begin();
if (aggregation_.empty()) {
// 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()) {
auto *pull_memory = context.evaluation_context.memory;
// place default aggregation values on the frame
for (const auto &elem : self_.aggregations_)
@@ -2829,6 +2813,7 @@ class AggregateCursor : public Cursor {
*value_it = 1;
break;
case Aggregation::Op::COLLECT_LIST:
case Aggregation::Op::PROJECT:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::COLLECT_MAP:
@@ -2877,6 +2862,7 @@ class AggregateCursor : public Cursor {
*value_it = *value_it + input_value;
break;
case Aggregation::Op::COLLECT_LIST:
case Aggregation::Op::PROJECT:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::COLLECT_MAP:

View File

@@ -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, const InterpreterConfig::Query &query_config,
DbAccessor *db_accessor, utils::SpinLock *antlr_lock, 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, query_config)},
parsed_statements_{ParseQuery(query, user_parameters, query_cache, antlr_lock, query_config)},
event_type_{event_type},
owner_{std::move(owner)} {
// We check immediately if the query is valid by trying to create a plan.
@@ -257,7 +257,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,
const InterpreterConfig::Query &query_config,
utils::SpinLock *antlr_lock, 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 +317,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, query_config,
std::move(owner), auth_checker);
trigger.emplace(trigger_name, statement, user_parameters, event_type, query_cache, db_accessor, antlr_lock,
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 +336,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,
const InterpreterConfig::Query &query_config, std::optional<std::string> owner,
const query::AuthChecker *auth_checker) {
utils::SpinLock *antlr_lock, 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 +345,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, query_config,
std::move(owner), auth_checker);
trigger.emplace(std::move(name), query, user_parameters, event_type, query_cache, db_accessor, antlr_lock,
query_config, std::move(owner), auth_checker);
} catch (const utils::BasicException &e) {
const auto identifiers = GetPredefinedIdentifiers(event_type);
std::stringstream identifier_names_stream;

View File

@@ -34,7 +34,7 @@ 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::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);
@@ -81,13 +81,14 @@ struct TriggerStore {
explicit TriggerStore(std::filesystem::path directory);
void RestoreTriggers(utils::SkipList<QueryCacheEntry> *query_cache, DbAccessor *db_accessor,
const InterpreterConfig::Query &query_config, const query::AuthChecker *auth_checker);
utils::SpinLock *antlr_lock, 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,
const InterpreterConfig::Query &query_config, std::optional<std::string> owner,
const query::AuthChecker *auth_checker);
utils::SpinLock *antlr_lock, const InterpreterConfig::Query &query_config,
std::optional<std::string> owner, const query::AuthChecker *auth_checker);
void DropTrigger(const std::string &name);

View File

@@ -73,16 +73,6 @@ 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
@@ -124,16 +114,6 @@ 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
@@ -175,16 +155,6 @@ 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
@@ -226,16 +196,6 @@ 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
@@ -277,16 +237,6 @@ 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
@@ -329,18 +279,3 @@ 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 |

View File

@@ -20,15 +20,6 @@
#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) {

View File

@@ -17,6 +17,3 @@ 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)

View File

@@ -26,11 +26,6 @@ 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"],
@@ -47,35 +42,30 @@ 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__))
@@ -111,7 +101,8 @@ 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]
@@ -121,10 +112,11 @@ 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))
@@ -133,54 +125,39 @@ 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:
@@ -189,7 +166,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!"
@@ -197,12 +174,10 @@ 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

View File

@@ -1,79 +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 <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;
}

View File

@@ -468,9 +468,8 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto fine_grained_perms = FineGrainedAccessPermissions();
auto user1 = User("test", "pw", perms, fine_grained_perms);
auto user2 = User("Test", "pw", perms, fine_grained_perms);
auto user1 = User("test", "pw", perms);
auto user2 = User("Test", "pw", perms);
ASSERT_EQ(user1, user2);
ASSERT_EQ(user1.username(), user2.username());
ASSERT_EQ(user1.username(), "test");
@@ -486,9 +485,8 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto fine_grained_perms = FineGrainedAccessPermissions();
auto role1 = Role("role", perms, fine_grained_perms);
auto role2 = Role("Role", perms, fine_grained_perms);
auto role1 = Role("role", perms);
auto role2 = Role("Role", perms);
ASSERT_EQ(role1, role2);
ASSERT_EQ(role1.rolename(), role2.rolename());
ASSERT_EQ(role1.rolename(), "role");

View File

@@ -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,7 @@ 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, labels) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), (labels))
#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 DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames))
#define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__)

View File

@@ -217,40 +217,31 @@ TEST_F(QueryPlanAggregateOps, WithData) {
TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) {
{
auto results = AggregationResults(true, {Aggregation::Op::COUNT});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
EXPECT_EQ(results[0][0].ValueInt(), 0);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::SUM});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
EXPECT_EQ(results[0][0].ValueInt(), 0);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::AVG});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::MIN});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::MAX});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_LIST});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::List);
EXPECT_EQ(results.size(), 0);
}
{
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_MAP});
EXPECT_EQ(results.size(), 1);
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map);
EXPECT_EQ(results.size(), 0);
}
}
@@ -269,7 +260,7 @@ TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) {
// max
EXPECT_TRUE(results[0][3].IsNull());
// sum
EXPECT_EQ(results[0][4].ValueInt(), 0);
EXPECT_TRUE(results[0][4].IsNull());
// avg
EXPECT_TRUE(results[0][5].IsNull());
// collect list

View File

@@ -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>{},
std::vector<std::string>{});
auto *query =
AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{});
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::AUTH));
}

View File

@@ -891,6 +891,7 @@ 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:
@@ -908,7 +909,7 @@ TEST_F(TriggerStoreTest, Restore) {
const auto reset_store = [&] {
store.emplace(testing_directory);
store->RestoreTriggers(&ast_cache, &*dba, memgraph::query::InterpreterConfig::Query{}, &auth_checker);
store->RestoreTriggers(&ast_cache, &*dba, &antlr_lock, memgraph::query::InterpreterConfig::Query{}, &auth_checker);
};
reset_store();
@@ -929,12 +930,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,
event_type, memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
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,
event_type, memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
memgraph::query::InterpreterConfig::Query{}, {owner}, &auth_checker);
const auto check_triggers = [&] {
@@ -985,16 +986,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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
memgraph::utils::BasicException);
@@ -1002,15 +1003,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,
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker));
&antlr_lock, 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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
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,
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker),
memgraph::utils::BasicException);
@@ -1026,7 +1027,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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker);
ASSERT_THROW(store.DropTrigger("Unknown"), memgraph::utils::BasicException);
@@ -1039,7 +1040,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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
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});
@@ -1059,7 +1060,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,
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
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});
@@ -1173,7 +1174,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,
memgraph::query::TriggerPhase::BEFORE_COMMIT, &ast_cache, &*dba, &antlr_lock,
memgraph::query::InterpreterConfig::Query{}, std::nullopt, &auth_checker));
store.DropTrigger(trigger_name);
}
@@ -1198,23 +1199,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,
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
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,
memgraph::query::TriggerPhase::AFTER_COMMIT, &ast_cache, &*dba, &antlr_lock,
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, 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, &antlr_lock,
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)))
@@ -1222,8 +1223,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, memgraph::query::InterpreterConfig::Query{}, &mock_checker));
ASSERT_NO_THROW(store->RestoreTriggers(&ast_cache, &*dba, &antlr_lock, memgraph::query::InterpreterConfig::Query{},
&mock_checker));
const auto triggers = store->GetTriggerInfo();
ASSERT_EQ(triggers.size(), 1);