Compare commits

..

32 Commits

Author SHA1 Message Date
Josip Mrden
2ff58aa783 Add value as pointer 2024-03-21 15:40:54 +01:00
Josip Mrden
e143d68c24 Added exception handling when pulling from interpreter 2024-03-21 15:29:57 +01:00
Josip Mrden
04f93ab46c Add support when query returns nothing 2024-03-21 15:21:51 +01:00
Josip Mrden
9ec55ac099 Fix 2024-03-21 13:16:55 +01:00
Josip Mrden
c85e510faa Add fetching values from the execution row 2024-03-21 13:14:50 +01:00
Josip Mrden
75d8a216c3 Add const to exception 2024-03-21 12:44:26 +01:00
Josip Mrden
358c34d444 pImpl 2024-03-20 14:21:01 +01:00
Josip Mrden
f517f8f368 Fix 2024-03-20 13:33:09 +01:00
Josip Mrden
7f335052be Add pull execution 2024-03-15 15:26:04 +01:00
Josip Mrden
53458b4962 Merge branch 'master' into query-execution-in-mage 2024-03-15 13:14:05 +01:00
Josip Mrden
2416d0fd15 Add friend class to map so query execution can access a private member 2024-03-01 13:27:00 +01:00
Josip Mrden
065c88612d Add another private section to QueryExecution 2024-03-01 13:21:18 +01:00
Josip Mrden
ed3a8a9328 Add query params execution 2024-03-01 13:19:14 +01:00
Josip Mrden
34f2a1e10b Change condition on has more rows 2024-03-01 12:57:18 +01:00
Josip Mrden
ce9bba8e83 Add has more for fetching optional result if no more rows to fetch 2024-03-01 12:55:41 +01:00
Josip Mrden
671f51f21d Row member instead of values 2024-03-01 12:38:30 +01:00
Josip Mrden
60de9e5a25 Add proxy methods to execution row 2024-02-29 18:03:59 +01:00
Josip Mrden
90fa6d9226 Add pull mechanism 2024-02-29 17:54:46 +01:00
Josip Mrden
4d930fb73b Model execution rows 2024-02-29 12:49:59 +01:00
Josip Mrden
d30a7c70de Add iterator methods for iteration 2024-02-29 11:49:36 +01:00
Josip Mrden
c0483576db Add headers iterator 2024-02-29 11:31:33 +01:00
Josip Mrden
6536d3b21d Add headers as return type 2024-02-28 23:32:52 +01:00
Josip Mrden
ec3ba6a408 Add return value when executing query in mgp 2024-02-28 23:30:06 +01:00
Josip Mrden
1145ea87ad Add support for headers 2024-02-28 23:23:15 +01:00
Josip Mrden
8fdc199d2b Implement headers in mgp 2024-02-28 17:08:17 +01:00
Josip Mrden
112c4528d3 Fixed condition for mg assert 2024-02-28 15:13:37 +01:00
Josip Mrden
11299981df Add mg invoke returning of result 2024-02-28 15:03:55 +01:00
Josip Mrden
2c0fc680de Created query execution result 2024-02-28 14:56:38 +01:00
Josip Mrden
2b30ba3fef Update method constness 2024-02-28 14:04:00 +01:00
Josip Mrden
d0babcddc5 Pass user to query execution 2024-02-28 13:39:41 +01:00
Josip Mrden
8ba1f160d4 Add API for executing a query 2024-02-28 13:07:59 +01:00
Josip Mrden
b6a55e534b Add Interpreter Context as singleton 2024-02-28 10:56:51 +01:00
37 changed files with 530 additions and 389 deletions

View File

@@ -4,10 +4,6 @@ concurrency:
cancel-in-progress: true
on:
push:
branches:
- master
workflow_dispatch:
pull_request:
paths-ignore:
- "docs/**"
@@ -475,8 +471,8 @@ jobs:
THREADS: 24
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
OS: debian-12
TOOLCHAIN: v5
OS: debian-10
TOOLCHAIN: v4
ARCH: amd
BUILD_TYPE: RelWithDebInfo

View File

@@ -5,20 +5,17 @@ IFS=' '
# NOTE: docker_image_name could be local image build based on release/package images.
# NOTE: each line has to be under quotes, docker_container_type, script_name and docker_image_name separate with a space.
# "docker_container_type script_name docker_image_name"
# docker_container_type OPTIONS:
# * mgrun -> running plain/empty operating system for the purposes of testing native memgraph package
# * mgbuild -> running the builder container to build memgraph inside it -> it's possible create builder images using release/package/run.sh
OPERATING_SYSTEMS=(
# "mgrun amzn-2 amazonlinux:2"
# "mgrun centos-7 centos:7"
# "mgrun centos-9 dokken/centos-stream-9"
# "mgrun debian-10 debian:10"
# "mgrun debian-11 debian:11"
# "mgrun fedora-36 fedora:36"
# "mgrun ubuntu-18.04 ubuntu:18.04"
# "mgrun ubuntu-20.04 ubuntu:20.04"
# "mgrun ubuntu-22.04 ubuntu:22.04"
# "mgbuild debian-12 memgraph/memgraph-builder:v5_debian-12"
"mgrun amzn-2 amazonlinux:2"
"mgrun centos-7 centos:7"
"mgrun centos-9 dokken/centos-stream-9"
"mgrun debian-10 debian:10"
"mgrun debian-11 debian:11"
"mgrun fedora-36 fedora:36"
"mgrun ubuntu-18.04 ubuntu:18.04"
"mgrun ubuntu-20.04 ubuntu:20.04"
"mgrun ubuntu-22.04 ubuntu:22.04"
# "mgbuild centos-7 package-mgbuild_centos-7"
)
if [ ! "$(docker info)" ]; then
@@ -36,24 +33,14 @@ print_help () {
# NOTE: This is an idempotent operation!
# TODO(gitbuda): Consider making docker_run always delete + start a new container or add a new function.
docker_run () {
cnt_type="$1"
if [[ "$cnt_type" != "mgbuild" && "$cnt_type" != "mgrun" ]]; then
echo "ERROR: Wrong docker_container_type -> valid options are mgbuild, mgrun"
exit 1
fi
cnt_name="$2"
cnt_image="$3"
cnt_name="$1"
cnt_image="$2"
if [ ! "$(docker ps -q -f name=$cnt_name)" ]; then
if [ "$(docker ps -aq -f status=exited -f name=$cnt_name)" ]; then
echo "Cleanup of the old exited container..."
docker rm $cnt_name
fi
if [[ "$cnt_type" == "mgbuild" ]]; then
docker run -d --volume "$SCRIPT_DIR/../../:/memgraph" --network host --name "$cnt_name" "$cnt_image"
fi
if [[ "$cnt_type" == "mgrun" ]]; then
docker run -d --volume "$SCRIPT_DIR/../../:/memgraph" --network host --name "$cnt_name" "$cnt_image" sleep infinity
fi
docker run -d --volume "$SCRIPT_DIR/../../:/memgraph" --network host --name "$cnt_name" "$cnt_image" sleep infinity
fi
echo "The $cnt_image container is active under $cnt_name name!"
}
@@ -68,9 +55,9 @@ docker_stop_and_rm () {
cnt_name="$1"
if [ "$(docker ps -q -f name=$cnt_name)" ]; then
docker stop "$1"
fi
if [ "$(docker ps -aq -f status=exited -f name=$cnt_name)" ]; then
docker rm "$1"
if [ "$(docker ps -aq -f status=exited -f name=$cnt_name)" ]; then
docker rm "$1"
fi
fi
}
@@ -84,7 +71,7 @@ start_all () {
docker_name="${docker_container_type}_$script_name"
echo ""
echo "~~~~ OPERATING ON $docker_image as $docker_name..."
docker_run "$docker_container_type" "$docker_name" "$docker_image"
docker_run "$docker_name" "$docker_image"
docker_exec "$docker_name" "/memgraph/environment/os/$script_name.sh install NEW_DEPS"
echo "---- DONE EVERYHING FOR $docker_image as $docker_name..."
echo ""

View File

@@ -851,4 +851,23 @@ inline void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_me
MgInvokeVoid(mgp_func_result_set_value, res, value, memory);
}
inline mgp_execution_result *execute_query(mgp_graph *graph, const char *query, mgp_map *params, mgp_memory *memory) {
return MgInvoke<mgp_execution_result *>(mgp_execute_query, graph, memory, query, params);
}
inline mgp_execution_headers *fetch_execution_headers(mgp_execution_result *exec_result) {
return MgInvoke<mgp_execution_headers *>(mgp_fetch_execution_headers, exec_result);
}
inline size_t execution_headers_size(mgp_execution_headers *headers) {
return MgInvoke<size_t>(mgp_execution_headers_size, headers);
}
inline const char *execution_headers_at(mgp_execution_headers *headers, size_t index) {
return MgInvoke<const char *>(mgp_execution_headers_at, headers, index);
}
inline mgp_map *pull_one(mgp_execution_result *result, mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_map *>(mgp_pull_one, result, graph, memory);
}
} // namespace mgp

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@@ -1800,6 +1800,24 @@ enum mgp_error mgp_func_result_set_error_msg(struct mgp_func_result *result, con
/// mgp_func_result.
enum mgp_error mgp_func_result_set_value(struct mgp_func_result *result, struct mgp_value *value,
struct mgp_memory *memory);
struct mgp_execution_headers;
enum mgp_error mgp_execution_headers_at(struct mgp_execution_headers *headers, size_t index, const char **result);
enum mgp_error mgp_execution_headers_size(struct mgp_execution_headers *headers, size_t *result);
struct mgp_execution_result;
enum mgp_error mgp_execute_query(struct mgp_graph *graph, struct mgp_memory *memory, const char *query,
struct mgp_map *params, struct mgp_execution_result **result);
enum mgp_error mgp_fetch_execution_headers(struct mgp_execution_result *exec_result,
struct mgp_execution_headers **headers);
enum mgp_error mgp_pull_one(struct mgp_execution_result *exec_result, struct mgp_graph *graph,
struct mgp_memory *memory, struct mgp_map **result);
/// @}
#ifdef __cplusplus

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@@ -16,6 +16,7 @@
#include <functional>
#include <map>
#include <mutex>
#include <optional>
#include <set>
#include <shared_mutex>
#include <string>
@@ -93,6 +94,10 @@ class Relationship;
struct MapItem;
class Duration;
class Value;
class QueryExecution;
class ExecutionResult;
class ExecutionHeaders;
class ExecutionRow;
struct StealType {};
inline constexpr StealType steal{};
@@ -595,6 +600,7 @@ class Map {
friend class Record;
friend class Result;
friend class Parameter;
friend class QueryExecution;
public:
/// @brief Creates a Map from the copy of the given @ref mgp_map.
@@ -1550,6 +1556,95 @@ class Return {
mgp_type *GetMGPType() const;
};
class ExecutionHeaders {
public:
ExecutionHeaders(mgp_execution_headers *headers);
size_t Size() const;
std::string At(size_t index) const;
std::string_view operator[](size_t index) const;
class Iterator {
private:
friend class ExecutionHeaders;
public:
using value_type = ExecutionHeaders;
using difference_type = std::ptrdiff_t;
using pointer = const ExecutionHeaders *;
using reference = const ExecutionHeaders &;
using iterator_category = std::forward_iterator_tag;
bool operator==(const Iterator &other) const;
bool operator!=(const Iterator &other) const;
Iterator &operator++();
std::string_view operator*() const;
private:
Iterator(const ExecutionHeaders *iterable, size_t index);
const ExecutionHeaders *iterable_;
size_t index_;
};
Iterator begin();
Iterator end();
Iterator cbegin();
Iterator cend();
private:
mgp_execution_headers *headers_;
};
class QueryExecution {
public:
QueryExecution(mgp_graph *graph);
ExecutionResult ExecuteQuery(std::string_view query, Map params = Map()) const;
private:
mgp_graph *graph_;
};
class ExecutionRow {
private:
Map row_;
public:
ExecutionRow(mgp_map *row);
/// @brief Returns the size of the map.
size_t Size() const;
/// @brief Returns whether the map is empty.
bool Empty() const;
/// @brief Returns the value at the given `key`.
Value operator[](std::string_view key) const;
/// @brief Returns the value at the given `key`.
Value At(std::string_view key) const;
/// @brief Returns true if the given `key` exists.
bool KeyExists(std::string_view key) const;
mgp::Map Values() const;
};
class ExecutionResult {
public:
ExecutionResult(mgp_execution_result *result, mgp_graph *graph);
ExecutionHeaders Headers() const;
std::optional<ExecutionRow> PullOne() const;
private:
mgp_execution_result *result_;
mgp_graph *graph_;
};
enum class ProcedureType : uint8_t {
Read,
Write,
@@ -4286,6 +4381,76 @@ inline mgp_type *Return::GetMGPType() const {
return util::ToMGPType(type_);
}
inline ExecutionHeaders::ExecutionHeaders(mgp_execution_headers *headers) : headers_(headers) {}
inline size_t ExecutionHeaders::Size() const { return mgp::execution_headers_size(headers_); }
inline std::string ExecutionHeaders::At(size_t index) const {
return std::string(mgp::execution_headers_at(headers_, index));
}
inline QueryExecution::QueryExecution(mgp_graph *graph) : graph_(graph) {}
inline ExecutionResult QueryExecution::ExecuteQuery(std::string_view query, mgp::Map params) const {
return ExecutionResult(mgp::MemHandlerCallback(execute_query, graph_, query.data(), params.ptr_), graph_);
}
inline ExecutionResult::ExecutionResult(mgp_execution_result *result, mgp_graph *graph)
: result_(result), graph_(graph) {}
inline ExecutionHeaders ExecutionResult::Headers() const { return mgp::fetch_execution_headers(result_); };
inline std::optional<ExecutionRow> ExecutionResult::PullOne() const {
auto *value = mgp::MemHandlerCallback(pull_one, result_, graph_);
if (!value) {
return std::nullopt;
}
return ExecutionRow(value);
}
inline bool ExecutionHeaders::Iterator::operator==(const Iterator &other) const {
return iterable_ == other.iterable_ && index_ == other.index_;
}
inline bool ExecutionHeaders::Iterator::operator!=(const Iterator &other) const { return !(*this == other); }
inline ExecutionHeaders::Iterator &ExecutionHeaders::Iterator::operator++() {
index_++;
return *this;
}
inline std::string_view ExecutionHeaders::Iterator::operator*() const { return (*iterable_)[index_]; }
inline ExecutionHeaders::Iterator::Iterator(const ExecutionHeaders *iterable, size_t index)
: iterable_(iterable), index_(index) {}
inline std::string_view ExecutionHeaders::operator[](size_t index) const {
return std::string_view(mgp::execution_headers_at(headers_, index));
}
inline ExecutionHeaders::Iterator ExecutionHeaders::begin() { return Iterator(this, 0); }
inline ExecutionHeaders::Iterator ExecutionHeaders::end() { return Iterator(this, Size()); }
inline ExecutionHeaders::Iterator ExecutionHeaders::cbegin() { return Iterator(this, 0); }
inline ExecutionHeaders::Iterator ExecutionHeaders::cend() { return Iterator(this, Size()); }
inline ExecutionRow::ExecutionRow(mgp_map *row) : row_(row) {}
inline size_t ExecutionRow::Size() const { return row_.Size(); }
inline bool ExecutionRow::Empty() const { return row_.Empty(); }
inline Value ExecutionRow::operator[](std::string_view key) const { return row_[key]; }
inline Value ExecutionRow::At(std::string_view key) const { return row_.At(key); }
inline bool ExecutionRow::KeyExists(std::string_view key) const { return row_.KeyExists(key); }
inline mgp::Map ExecutionRow::Values() const { return mgp::Map(row_); }
// do not enter
namespace detail {
inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> &parameters,

View File

@@ -1,5 +1,4 @@
/etc/memgraph/memgraph.conf
/etc/memgraph/apoc_compatibility_mappings.json
/etc/memgraph/auth_module/ldap.example.yaml
/etc/memgraph/auth_module/saml/settings.json
/etc/logrotate.d/memgraph

View File

@@ -211,7 +211,6 @@ check_support() {
build_memgraph () {
local build_container="mgbuild_${toolchain_version}_${os}"
local ACTIVATE_TOOLCHAIN="source /opt/toolchain-${toolchain_version}/activate"
local ACTIVATE_CARGO="source $MGBUILD_HOME_DIR/.cargo/env"
local container_build_dir="$MGBUILD_ROOT_DIR/build"
local container_output_dir="$container_build_dir/output"
local arm_flag=""
@@ -317,7 +316,7 @@ build_memgraph () {
# Define cmake command
local cmake_cmd="cmake $build_type_flag $arm_flag $community_flag $telemetry_id_override_flag $coverage_flag $asan_flag $ubsan_flag .."
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $ACTIVATE_CARGO && $cmake_cmd"
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $cmake_cmd"
# ' is used instead of " because we need to run make within the allowed
# container resources.
@@ -327,11 +326,11 @@ build_memgraph () {
# support nproc
# shellcheck disable=SC2016
if [[ "$threads" == 0 ]]; then
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $ACTIVATE_CARGO "'&& make -j$(nproc)'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $ACTIVATE_CARGO "'&& make -j$(nproc) -B mgconsole'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN "'&& make -j$(nproc)'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN "'&& make -j$(nproc) -B mgconsole'
else
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $ACTIVATE_CARGO "'&& make -j$threads'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && $ACTIVATE_CARGO "'&& make -j$threads -B mgconsole'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN "'&& make -j$threads'
docker exec -u mg "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN "'&& make -j$threads -B mgconsole'
fi
}
@@ -397,7 +396,6 @@ copy_memgraph() {
test_memgraph() {
local ACTIVATE_TOOLCHAIN="source /opt/toolchain-${toolchain_version}/activate"
local ACTIVATE_VENV="./setup.sh /opt/toolchain-${toolchain_version}/activate"
local ACTIVATE_CARGO="source $MGBUILD_HOME_DIR/.cargo/env"
local EXPORT_LICENSE="export MEMGRAPH_ENTERPRISE_LICENSE=$enterprise_license"
local EXPORT_ORG_NAME="export MEMGRAPH_ORGANIZATION_NAME=$organization_name"
local BUILD_DIR="$MGBUILD_ROOT_DIR/build"
@@ -483,7 +481,7 @@ test_memgraph() {
# docker network connect --alias $kafka_hostname $build_container_network $kafka_container > /dev/null 2>&1 || echo "Kafka container already inside correct network or something went wrong ..."
# docker network connect --alias $pulsar_hostname $build_container_network $pulsar_container > /dev/null 2>&1 || echo "Kafka container already inside correct network or something went wrong ..."
docker exec -u mg $build_container bash -c "pip install --user networkx && pip3 install --user networkx"
docker exec -u mg $build_container bash -c "$EXPORT_LICENSE && $EXPORT_ORG_NAME && $ACTIVATE_CARGO && cd $MGBUILD_ROOT_DIR/tests && $ACTIVATE_VENV && source ve3/bin/activate_e2e && cd $MGBUILD_ROOT_DIR/tests/e2e "'&& ./run.sh'
docker exec -u mg $build_container bash -c "$EXPORT_LICENSE && $EXPORT_ORG_NAME && cd $MGBUILD_ROOT_DIR/tests && $ACTIVATE_VENV && source ve3/bin/activate_e2e && cd $MGBUILD_ROOT_DIR/tests/e2e "'&& ./run.sh'
;;
*)
echo "Error: Unknown test '$1'"

View File

@@ -134,7 +134,6 @@ echo "Don't forget to switch to the 'memgraph' user to use Memgraph" || exit 1
%config(noreplace) "/etc/memgraph/memgraph.conf"
%config(noreplace) "/etc/memgraph/apoc_compatibility_mappings.json"
%config(noreplace) "/etc/memgraph/auth_module/ldap.example.yaml"
%config(noreplace) "/etc/memgraph/auth_module/saml/settings.json"
%config(noreplace) "/etc/logrotate.d/memgraph"
@CPACK_RPM_USER_INSTALL_FILES@

View File

@@ -29,7 +29,3 @@ install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/reference_modules/ldap.py
DESTINATION lib/memgraph/auth_module)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/reference_modules/ldap.example.yaml
DESTINATION /etc/memgraph/auth_module)
install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/reference_modules/saml/saml.py
DESTINATION lib/memgraph/auth_module/saml)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/reference_modules/saml/settings.json
DESTINATION /etc/memgraph/auth_module/saml)

File diff suppressed because one or more lines are too long

View File

@@ -1,30 +0,0 @@
{
"strict": false,
"debug": true,
"sp": {
"entityId": "http://localhost:5000/metadata/",
"assertionConsumerService": {
"url": "http://localhost:8000/?acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
"singleLogoutService": {
"url": "http://localhost:5000/?sls",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
"x509cert": "",
"privateKey": ""
},
"idp": {
"entityId": "https://saml.example.com/entityid",
"singleSignOnService": {
"url": "https://mocksaml.com/api/saml/sso",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"singleLogoutService": {
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id>",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"x509cert": "MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJVSzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4MjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQKDAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0RuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3VpwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZNfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4khuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeXUjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8Lr/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99Mm0eo2USlSRTVl7QHRTuiuSThHpLKQQ=="
}
}

View File

@@ -131,10 +131,6 @@ DEFINE_uint64(storage_recovery_thread_count,
DEFINE_bool(storage_enable_schema_metadata, false,
"Controls whether metadata should be collected about the resident labels and edge types.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_delta_on_identical_property_update, true,
"Controls whether updating a property with the same value should create a delta object.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(telemetry_enabled, false,
"Set to true to enable telemetry. We collect information about the "

View File

@@ -84,8 +84,6 @@ DECLARE_bool(storage_parallel_schema_recovery);
DECLARE_uint64(storage_recovery_thread_count);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_enable_schema_metadata);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_delta_on_identical_property_update);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(telemetry_enabled);

View File

@@ -332,8 +332,7 @@ int main(int argc, char **argv) {
.durability_directory = FLAGS_data_directory + "/rocksdb_durability",
.wal_directory = FLAGS_data_directory + "/rocksdb_wal"},
.salient.items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
.enable_schema_metadata = FLAGS_storage_enable_schema_metadata,
.delta_on_identical_property_update = FLAGS_storage_delta_on_identical_property_update},
.enable_schema_metadata = FLAGS_storage_enable_schema_metadata},
.salient.storage_mode = memgraph::flags::ParseStorageMode()};
spdlog::info("config recover on startup {}, flags {} {}", db_config.durability.recover_on_startup,
FLAGS_storage_recover_on_startup, FLAGS_data_recovery_on_startup);
@@ -437,12 +436,12 @@ int main(int argc, char **argv) {
auto db_acc = dbms_handler.Get();
memgraph::query::InterpreterContext interpreter_context_(interp_config, &dbms_handler, &repl_state, system,
auto *interpreter_context_ =
memgraph::query::InterpreterContext::getInstance(interp_config, &dbms_handler, &repl_state, system,
#ifdef MG_ENTERPRISE
&coordinator_state,
&coordinator_state,
#endif
auth_handler.get(), auth_checker.get(),
&replication_handler);
auth_handler.get(), auth_checker.get(), &replication_handler);
MG_ASSERT(db_acc, "Failed to access the main database");
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(memgraph::flags::ParseQueryModulesDirectory(),
@@ -455,9 +454,9 @@ int main(int argc, char **argv) {
spdlog::info("Running init file...");
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_file, &audit_log);
InitFromCypherlFile(*interpreter_context_, db_acc, FLAGS_init_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_file);
InitFromCypherlFile(*interpreter_context_, db_acc, FLAGS_init_file);
}
#else
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_file);
@@ -465,20 +464,20 @@ int main(int argc, char **argv) {
}
#ifdef MG_ENTERPRISE
dbms_handler.RestoreTriggers(&interpreter_context_);
dbms_handler.RestoreStreams(&interpreter_context_);
dbms_handler.RestoreTriggers(interpreter_context_);
dbms_handler.RestoreStreams(interpreter_context_);
#else
{
// Triggers can execute query procedures, so we need to reload the modules first and then
// the triggers
auto storage_accessor = db_acc->Access();
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
db_acc->trigger_store()->RestoreTriggers(&interpreter_context_.ast_cache, &dba, interpreter_context_.config.query,
db_acc->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.
db_acc->streams()->RestoreStreams(db_acc, &interpreter_context_);
db_acc->streams()->RestoreStreams(db_acc, interpreter_context_);
#endif
ServerContext context;
@@ -494,9 +493,9 @@ int main(int argc, char **argv) {
auto server_endpoint = memgraph::communication::v2::ServerEndpoint{
boost::asio::ip::address::from_string(FLAGS_bolt_address), static_cast<uint16_t>(FLAGS_bolt_port)};
#ifdef MG_ENTERPRISE
Context session_context{&interpreter_context_, &auth_, &audit_log};
Context session_context{interpreter_context_, &auth_, &audit_log};
#else
Context session_context{&interpreter_context_, &auth_};
Context session_context{interpreter_context_, &auth_};
#endif
memgraph::glue::ServerT server(server_endpoint, &session_context, &context, FLAGS_bolt_session_inactivity_timeout,
service_name, FLAGS_bolt_num_workers);
@@ -541,14 +540,14 @@ int main(int argc, char **argv) {
#ifdef MG_ENTERPRISE
&metrics_server,
#endif
&websocket_server, &server, &interpreter_context_] {
&websocket_server, &server, interpreter_context_] {
// Server needs to be shutdown first and then the database. This prevents
// a race condition when a transaction is accepted during server shutdown.
server.Shutdown();
// After the server is notified to stop accepting and processing
// connections we tell the execution engine to stop processing all pending
// queries.
interpreter_context_.Shutdown();
interpreter_context_->Shutdown();
websocket_server.Shutdown();
#ifdef MG_ENTERPRISE
metrics_server.Shutdown();
@@ -576,12 +575,12 @@ int main(int argc, char **argv) {
MG_ASSERT(db_acc, "Failed to gain access to the main database");
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file, &audit_log);
InitFromCypherlFile(*interpreter_context_, db_acc, FLAGS_init_data_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
InitFromCypherlFile(*interpreter_context_, db_acc, FLAGS_init_data_file);
}
#else
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
InitFromCypherlFile(*interpreter_context_, db_acc, FLAGS_init_data_file);
#endif
}

View File

@@ -72,6 +72,12 @@ inline std::vector<storage::LabelId> NamesToLabels(const std::vector<std::string
return labels;
}
struct UserExecutionContextInfo {
enum class UserMode { NONE, USER, ROLE };
UserMode mode;
std::string name;
};
struct ExecutionContext {
DbAccessor *db_accessor{nullptr};
SymbolTable symbol_table;
@@ -86,6 +92,7 @@ struct ExecutionContext {
TriggerContextCollector *trigger_context_collector{nullptr};
FrameChangeCollector *frame_change_collector{nullptr};
std::shared_ptr<utils::AsyncTimer> timer;
std::shared_ptr<QueryUserOrRole> user_or_role;
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
#endif

View File

@@ -1,4 +1,4 @@
// Copyright 2024 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source

View File

@@ -1754,10 +1754,12 @@ PullPlan::PullPlan(const std::shared_ptr<PlanWrapper> plan, const Parameters &pa
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);
ctx_.user_or_role = user_or_role;
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && user_or_role && *user_or_role && dba) {
// Create only if an explicit user is defined
auto auth_checker = interpreter_context->auth_checker->GetFineGrainedAuthChecker(std::move(user_or_role), dba);
auto auth_checker = interpreter_context->auth_checker->GetFineGrainedAuthChecker(user_or_role, dba);
// if the user has global privileges to read, edit and write anything, we don't need to perform authorization
// otherwise, we do assign the auth checker to check for label access control

View File

@@ -15,6 +15,8 @@
#include "system/include/system/system.hpp"
namespace memgraph::query {
InterpreterContext *InterpreterContext::instance = nullptr;
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, memgraph::system::System &system,
#ifdef MG_ENTERPRISE

View File

@@ -27,6 +27,7 @@
#include "storage/v2/transaction.hpp"
#include "system/state.hpp"
#include "system/system.hpp"
#include "utils/exceptions.hpp"
#include "utils/gatekeeper.hpp"
#include "utils/skip_list.hpp"
#include "utils/spin_lock.hpp"
@@ -54,13 +55,30 @@ struct QueryUserOrRole;
*
*/
struct InterpreterContext {
InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, memgraph::system::System &system,
static InterpreterContext *instance;
static InterpreterContext *getInstance() {
MG_ASSERT(instance != nullptr, "Interpreter context has not been initialized!");
return instance;
}
static InterpreterContext *getInstance(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, memgraph::system::System &system,
#ifdef MG_ENTERPRISE
memgraph::coordination::CoordinatorState *coordinator_state,
memgraph::coordination::CoordinatorState *coordinator_state,
#endif
AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr,
ReplicationQueryHandler *replication_handler = nullptr);
AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr,
ReplicationQueryHandler *replication_handler = nullptr) {
if (instance == nullptr) {
instance = new InterpreterContext(interpreter_config, dbms_handler, rs, system,
#ifdef MG_ENTERPRISE
coordinator_state,
#endif
ah, ac, replication_handler);
}
return instance;
}
memgraph::dbms::DbmsHandler *dbms_handler;
@@ -98,6 +116,14 @@ struct InterpreterContext {
std::vector<std::vector<TypedValue>> TerminateTransactions(
std::vector<std::string> maybe_kill_transaction_ids, QueryUserOrRole *user_or_role,
std::function<bool(QueryUserOrRole *, std::string const &)> privilege_checker);
};
private:
InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, memgraph::system::System &system,
#ifdef MG_ENTERPRISE
memgraph::coordination::CoordinatorState *coordinator_state,
#endif
AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr,
ReplicationQueryHandler *replication_handler = nullptr);
};
} // namespace memgraph::query

View File

@@ -329,7 +329,7 @@ CreateExpand::CreateExpand(NodeCreationInfo node_info, EdgeCreationInfo edge_inf
ACCEPT_WITH_INPUT(CreateExpand)
UniqueCursorPtr CreateExpand::MakeCursor(utils::MemoryResource *mem) const {
memgraph::metrics::IncrementCounter(memgraph::metrics::CreateExpandOperator);
memgraph::metrics::IncrementCounter(memgraph::metrics::CreateNodeOperator);
return MakeUniqueCursorPtr<CreateExpandCursor>(mem, *this, mem);
}

View File

@@ -23,11 +23,14 @@
#include <utility>
#include <variant>
#include "glue/auth.hpp"
#include "license/license.hpp"
#include "mg_procedure.h"
#include "module.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/procedure/cypher_types.hpp"
#include "query/procedure/fmt.hpp"
#include "query/procedure/mg_procedure_helpers.hpp"
@@ -4021,3 +4024,131 @@ mgp_error mgp_untrack_current_thread_allocations(mgp_graph *graph) {
std::visit([](auto *db_accessor) -> void { db_accessor->UntrackCurrentThreadAllocations(); }, graph->impl);
});
}
mgp_execution_headers::mgp_execution_headers(memgraph::utils::pmr::vector<memgraph::utils::pmr::string> &&storage)
: headers(std::move(storage)){};
mgp_error mgp_execution_headers_size(mgp_execution_headers *headers, size_t *result) {
static_assert(noexcept(headers->headers.size()));
*result = headers->headers.size();
return mgp_error::MGP_ERROR_NO_ERROR;
}
mgp_error mgp_execution_headers_at(mgp_execution_headers *headers, size_t index, const char **result) {
return WrapExceptions(
[headers, index] {
if (index >= Call<size_t>(mgp_execution_headers_size, headers)) {
throw std::out_of_range("Header cannot be retrieved, because index exceeds headers' size!");
}
return headers->headers[index].data();
},
result);
}
mgp_execution_rows::mgp_execution_rows(
memgraph::utils::pmr::vector<memgraph::utils::pmr::vector<memgraph::query::TypedValue>> &&tv_rows)
: rows(std::move(tv_rows)) {}
struct MgProcedureResultStream final {
explicit MgProcedureResultStream(mgp_memory *memory) : rows(memory->impl), memory(memory) {}
using Row = std::vector<memgraph::query::TypedValue>;
using Rows = std::vector<Row>;
using PmrRow = memgraph::utils::pmr::vector<memgraph::query::TypedValue>;
using PmrRows = memgraph::utils::pmr::vector<PmrRow>;
PmrRows rows;
mgp_memory *memory;
void Result(const Row &row) {
PmrRow pmr_row(memory->impl);
for (auto &val : row) {
pmr_row.emplace_back(std::move(val));
}
rows.emplace_back(std::move(pmr_row));
}
};
std::map<std::string, memgraph::storage::PropertyValue> CreateQueryParams(mgp_map *params) {
std::map<std::string, memgraph::storage::PropertyValue> query_params;
for (auto &[k, v] : params->items) {
query_params.emplace(k, ToPropertyValue(v));
}
return query_params;
}
struct mgp_execution_result::pImplMgpExecutionResult {
std::unique_ptr<memgraph::query::Interpreter> interpreter;
std::unique_ptr<mgp_execution_headers> headers;
};
mgp_execution_result::mgp_execution_result() : pImpl(std::make_unique<pImplMgpExecutionResult>()) {
auto *instance = memgraph::query::InterpreterContext::getInstance();
pImpl->interpreter = std::make_unique<memgraph::query::Interpreter>(instance, instance->dbms_handler->Get());
}
mgp_execution_result::~mgp_execution_result() {
auto *instance = memgraph::query::InterpreterContext::getInstance();
instance->interpreters.WithLock([this](auto &interpreters) { interpreters.erase(pImpl->interpreter.get()); });
// interpreter will delete itself because it's a smart pointer
}
mgp_error mgp_execute_query(mgp_graph *graph, mgp_memory *memory, const char *query, mgp_map *params,
mgp_execution_result **result) {
return WrapExceptions(
[query, params, graph, memory]() {
auto query_string = std::string(query);
auto *instance = memgraph::query::InterpreterContext::getInstance();
mgp_execution_result *result = NewRawMgpObject<mgp_execution_result>(memory->impl);
result->pImpl->interpreter->SetUser(graph->ctx->user_or_role);
instance->interpreters.WithLock(
[result](auto &interpreters) { interpreters.insert(result->pImpl->interpreter.get()); });
const auto query_params = CreateQueryParams(params);
auto prepare_query_result = result->pImpl->interpreter->Prepare(query_string, query_params, {});
memgraph::utils::pmr::vector<memgraph::utils::pmr::string> headers(memory->impl);
for (auto header : prepare_query_result.headers) {
headers.emplace_back(header);
}
result->pImpl->headers = std::make_unique<mgp_execution_headers>(std::move(headers));
return result;
},
result);
}
mgp_error mgp_fetch_execution_headers(mgp_execution_result *exec_result, mgp_execution_headers **result) {
return WrapExceptions([exec_result]() { return exec_result->pImpl->headers.get(); }, result);
}
mgp_error mgp_pull_one(mgp_execution_result *exec_result, mgp_graph *graph, mgp_memory *memory, mgp_map **result) {
return WrapExceptions(
[exec_result, graph, memory]() -> mgp_map * {
MgProcedureResultStream stream(memory);
try {
exec_result->pImpl->interpreter->Pull(&stream, 1, {});
} catch (const std::exception &e) {
return nullptr;
}
if (stream.rows.empty()) {
return nullptr;
}
const size_t headers_size = exec_result->pImpl->headers->headers.size();
memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_value> items(memory->impl);
for (size_t idx = 0; idx < headers_size; idx++) {
items.emplace(exec_result->pImpl->headers->headers[idx],
mgp_value{std::move(stream.rows[0][idx]), graph, memory->impl});
}
return NewRawMgpObject<mgp_map>(memory->impl, std::move(items));
},
result);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd.
// Copyright 2024 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
@@ -16,6 +16,7 @@
#include "mg_procedure.h"
#include <memory>
#include <optional>
#include <ostream>
@@ -24,6 +25,7 @@
#include "query/context.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/procedure/cypher_type_ptr.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/view.hpp"
@@ -33,6 +35,7 @@
#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
@@ -993,3 +996,29 @@ struct mgp_messages {
bool ContainsDeleted(const mgp_value *val);
memgraph::query::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils::MemoryResource *memory);
struct mgp_execution_headers {
using allocator_type = memgraph::utils::Allocator<mgp_execution_headers>;
using storage_type = memgraph::utils::pmr::vector<memgraph::utils::pmr::string>;
explicit mgp_execution_headers(storage_type &&storage);
~mgp_execution_headers() = default;
storage_type headers;
};
struct mgp_execution_rows {
explicit mgp_execution_rows(
memgraph::utils::pmr::vector<memgraph::utils::pmr::vector<memgraph::query::TypedValue>> &&tv_rows);
~mgp_execution_rows() = default;
memgraph::utils::pmr::vector<memgraph::utils::pmr::vector<memgraph::query::TypedValue>> rows;
};
struct mgp_execution_result {
explicit mgp_execution_result();
~mgp_execution_result();
struct pImplMgpExecutionResult;
std::unique_ptr<pImplMgpExecutionResult> pImpl;
};

View File

@@ -37,7 +37,6 @@ struct SalientConfig {
struct Items {
bool properties_on_edges{true};
bool enable_schema_metadata{false};
bool delta_on_identical_property_update{true};
friend bool operator==(const Items &lrh, const Items &rhs) = default;
} items;

View File

@@ -130,13 +130,9 @@ Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, co
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
using ReturnType = decltype(edge_.ptr->properties.GetProperty(property));
std::optional<ReturnType> current_value;
const bool skip_duplicate_write = !storage_->config_.salient.items.delta_on_identical_property_update;
utils::AtomicMemoryBlock atomic_memory_block{
[&current_value, &property, &value, transaction = transaction_, edge = edge_, skip_duplicate_write]() {
[&current_value, &property, &value, transaction = transaction_, edge = edge_]() {
current_value.emplace(edge.ptr->properties.GetProperty(property));
if (skip_duplicate_write && current_value == value) {
return;
}
// We could skip setting the value if the previous one is the same to the new
// one. This would save some memory as a delta would not be created as well as
// avoid copying the value. The reason we are not doing that is because the
@@ -188,14 +184,12 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAc
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
const bool skip_duplicate_write = !storage_->config_.salient.items.delta_on_identical_property_update;
using ReturnType = decltype(edge_.ptr->properties.UpdateProperties(properties));
std::optional<ReturnType> id_old_new_change;
utils::AtomicMemoryBlock atomic_memory_block{
[transaction_ = transaction_, edge_ = edge_, &properties, &id_old_new_change, skip_duplicate_write]() {
[transaction_ = transaction_, edge_ = edge_, &properties, &id_old_new_change]() {
id_old_new_change.emplace(edge_.ptr->properties.UpdateProperties(properties));
for (auto &[property, old_value, new_value] : *id_old_new_change) {
if (skip_duplicate_write && old_value == new_value) continue;
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(old_value));
}
}};

View File

@@ -261,31 +261,20 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
if (vertex_->deleted) return Error::DELETED_OBJECT;
PropertyValue current_value;
const bool skip_duplicate_write = !storage_->config_.salient.items.delta_on_identical_property_update;
utils::AtomicMemoryBlock atomic_memory_block{
[transaction = transaction_, vertex = vertex_, &value, &property, &current_value, skip_duplicate_write]() {
current_value = vertex->properties.GetProperty(property);
// We could skip setting the value if the previous one is the same to the new
// one. This would save some memory as a delta would not be created as well as
// avoid copying the value. The reason we are not doing that is because the
// current code always follows the logical pattern of "create a delta" and
// "modify in-place". Additionally, the created delta will make other
// transactions get a SERIALIZATION_ERROR.
if (skip_duplicate_write && current_value == value) {
return true;
}
auto current_value = vertex_->properties.GetProperty(property);
// We could skip setting the value if the previous one is the same to the new
// one. This would save some memory as a delta would not be created as well as
// avoid copying the value. The reason we are not doing that is because the
// current code always follows the logical pattern of "create a delta" and
// "modify in-place". Additionally, the created delta will make other
// transactions get a SERIALIZATION_ERROR.
utils::AtomicMemoryBlock atomic_memory_block{
[transaction = transaction_, vertex = vertex_, &value, &property, &current_value]() {
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, current_value);
vertex->properties.SetProperty(property, value);
return false;
}};
const bool early_exit = std::invoke(atomic_memory_block);
if (early_exit) {
return std::move(current_value);
}
std::invoke(atomic_memory_block);
if (transaction_->constraint_verification_info) {
if (!value.IsNull()) {
@@ -350,29 +339,27 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> Vertex
if (vertex_->deleted) return Error::DELETED_OBJECT;
const bool skip_duplicate_update = storage_->config_.salient.items.delta_on_identical_property_update;
using ReturnType = decltype(vertex_->properties.UpdateProperties(properties));
std::optional<ReturnType> id_old_new_change;
utils::AtomicMemoryBlock atomic_memory_block{[storage = storage_, transaction = transaction_, vertex = vertex_,
&properties, &id_old_new_change, skip_duplicate_update]() {
id_old_new_change.emplace(vertex->properties.UpdateProperties(properties));
if (!id_old_new_change.has_value()) {
return;
}
for (auto &[id, old_value, new_value] : *id_old_new_change) {
storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction);
if (skip_duplicate_update && old_value == new_value) continue;
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), id, std::move(old_value));
transaction->manyDeltasCache.Invalidate(vertex, id);
if (transaction->constraint_verification_info) {
if (!new_value.IsNull()) {
transaction->constraint_verification_info->AddedProperty(vertex);
} else {
transaction->constraint_verification_info->RemovedProperty(vertex);
utils::AtomicMemoryBlock atomic_memory_block{
[storage = storage_, transaction = transaction_, vertex = vertex_, &properties, &id_old_new_change]() {
id_old_new_change.emplace(vertex->properties.UpdateProperties(properties));
if (!id_old_new_change.has_value()) {
return;
}
}
}
}};
for (auto &[id, old_value, new_value] : *id_old_new_change) {
storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction);
CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), id, std::move(old_value));
transaction->manyDeltasCache.Invalidate(vertex, id);
if (transaction->constraint_verification_info) {
if (!new_value.IsNull()) {
transaction->constraint_verification_info->AddedProperty(vertex);
} else {
transaction->constraint_verification_info->RemovedProperty(vertex);
}
}
}
}};
std::invoke(atomic_memory_block);
return id_old_new_change.has_value() ? std::move(id_old_new_change.value()) : ReturnType{};

View File

@@ -29,10 +29,12 @@ class [[nodiscard]] AtomicMemoryBlock {
AtomicMemoryBlock &operator=(AtomicMemoryBlock &&) = delete;
~AtomicMemoryBlock() = default;
auto operator()() -> std::invoke_result_t<Callable> {
auto check_on_exit = OnScopeExit{[&] { total_memory_tracker.DoCheck(); }};
utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_blocker;
return function_();
void operator()() {
{
utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_blocker;
function_();
}
total_memory_tracker.DoCheck();
}
private:

View File

@@ -35,7 +35,7 @@ namespace memgraph::utils {
* // long block of code, might throw an exception
* }
*/
template <std::invocable Callable>
template <typename Callable>
class [[nodiscard]] OnScopeExit {
public:
template <typename U>
@@ -46,7 +46,7 @@ class [[nodiscard]] OnScopeExit {
OnScopeExit &operator=(OnScopeExit const &) = delete;
OnScopeExit &operator=(OnScopeExit &&) = delete;
~OnScopeExit() {
if (doCall_) std::invoke(std::move(function_));
if (doCall_) function_();
}
void Disable() { doCall_ = false; }
@@ -57,4 +57,5 @@ class [[nodiscard]] OnScopeExit {
};
template <typename Callable>
OnScopeExit(Callable &&) -> OnScopeExit<Callable>;
} // namespace memgraph::utils

View File

@@ -77,7 +77,6 @@ add_subdirectory(garbage_collection)
add_subdirectory(query_planning)
add_subdirectory(awesome_functions)
add_subdirectory(high_availability)
add_subdirectory(concurrency)
add_subdirectory(replication_experimental)

View File

@@ -1,6 +0,0 @@
function(copy_concurrency_e2e_python_files FILE_NAME)
copy_e2e_python_files(concurrency ${FILE_NAME})
endfunction()
copy_concurrency_e2e_python_files(common.py)
copy_concurrency_e2e_python_files(concurrency.py)

View File

@@ -1,60 +0,0 @@
# Copyright 2023 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import typing
import mgclient
import pytest
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
def execute_and_fetch_all_with_commit(
connection: mgclient.Connection, query: str, params: dict = {}
) -> typing.List[tuple]:
cursor = connection.cursor()
cursor.execute(query, params)
results = cursor.fetchall()
connection.commit()
return results
@pytest.fixture
def first_connection(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
cursor = connection.cursor()
execute_and_fetch_all(cursor, "USE DATABASE memgraph")
try:
execute_and_fetch_all(cursor, "DROP DATABASE clean")
except:
pass
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")
connection.autocommit = False
yield connection
@pytest.fixture
def second_connection(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
cursor = connection.cursor()
execute_and_fetch_all(cursor, "USE DATABASE memgraph")
try:
execute_and_fetch_all(cursor, "DROP DATABASE clean")
except:
pass
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")
connection.autocommit = False
yield connection

View File

@@ -1,57 +0,0 @@
# Copyright 2023 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import sys
import pytest
from common import execute_and_fetch_all, first_connection, second_connection
def test_concurrency_if_no_delta_on_same_node_property_update(first_connection, second_connection):
m1c = first_connection.cursor()
m2c = second_connection.cursor()
execute_and_fetch_all(m1c, "CREATE (:Node {prop: 1})")
first_connection.commit()
test_has_error = False
try:
m1c.execute("MATCH (n) SET n.prop = 1")
m2c.execute("MATCH (n) SET n.prop = 1")
first_connection.commit()
second_connection.commit()
except Exception as e:
test_has_error = True
assert test_has_error is False
def test_concurrency_if_no_delta_on_same_edge_property_update(first_connection, second_connection):
m1c = first_connection.cursor()
m2c = second_connection.cursor()
execute_and_fetch_all(m1c, "CREATE ()-[:TYPE {prop: 1}]->()")
first_connection.commit()
test_has_error = False
try:
m1c.execute("MATCH (n)-[r]->(m) SET r.prop = 1")
m2c.execute("MATCH (n)-[r]->(m) SET n.prop = 1")
first_connection.commit()
second_connection.commit()
except Exception as e:
test_has_error = True
assert test_has_error is False
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -1,14 +0,0 @@
concurrency_cluster: &concurrency_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE", "--storage-delta-on-identical-property-update=false"]
log_file: "concurrency.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Concurrency"
binary: "tests/e2e/pytest_runner.sh"
args: ["concurrency/concurrency.py"]
<<: *concurrency_cluster

View File

@@ -141,11 +141,6 @@ startup_config_dict = {
"1",
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: The MAIN instance allocates a new thread for each REPLICA.",
),
"storage_delta_on_identical_property_update": (
"true",
"true",
"Controls whether updating a property with the same value should create a delta object.",
),
"storage_gc_cycle_sec": ("30", "30", "Storage garbage collector interval (in seconds)."),
"storage_python_gc_cycle_sec": ("180", "180", "Storage python full garbage collection interval (in seconds)."),
"storage_items_per_batch": (

View File

@@ -0,0 +1,13 @@
diff --git a/docker/control/Dockerfile b/docker/control/Dockerfile
index 6b2d3c0e..195a7a60 100644
--- a/docker/control/Dockerfile
+++ b/docker/control/Dockerfile
@@ -7,7 +7,7 @@ ENV LEIN_ROOT true
# Jepsen dependencies
#
RUN apt-get -y -q update && \
- apt-get install -qy openjdk-17-jdk-headless \
+ apt-get install -qy ca-certificates-java openjdk-17-jdk-headless \
libjna-java \
vim \
emacs \

View File

@@ -5,10 +5,10 @@
:url "https://github.com/memgraph/memgraph/blob/master/release/LICENSE_ENTERPRISE.md"}
:main jepsen.memgraph.core
:dependencies [[org.clojure/clojure "1.10.0"]
;; Details under https://clojars.org/jepsen/versions.
[jepsen "0.3.5-SNAPSHOT"]
;; 0.2.4-SNAPSHOT but 0.3.0, for more -> https://clojars.org/jepsen/versions
[jepsen "0.2.4-SNAPSHOT"]
[gorillalabs/neo4j-clj "4.1.0"]]
:profiles {:test {:dependencies [#_[org.neo4j.test/neo4j-harness "4.1.0"]]}}
;; The below line is required to run after Jepsen 0.3.0.
:aot :all
;; required to run 0.3.0
; :aot :all
:repl-options {:init-ns jepsen.memgraph.core})

View File

@@ -2,10 +2,12 @@
set -Eeuo pipefail
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
MEMGRAPH_BUILD_PATH="$script_dir/../../build"
MEMGRAPH_BINARY_PATH="$MEMGRAPH_BUILD_PATH/memgraph"
MEMGRAPH_BINARY_PATH="../../build/memgraph"
# NOTE: Jepsen Git tags are not consistent, there are: 0.2.4, v0.3.0, 0.3.2, ...
JEPSEN_VERSION="${JEPSEN_VERSION:-v0.3.5}"
# NOTE: On Ubuntu 22.04 v0.3.2 uses non-existing docker compose --compatibility flag.
# NOTE: On Ubuntu 22.04 v0.3.0 and v0.3.1 seems to be runnable.
# TODO(gitbuda): Make sure Memgraph can be testes with Jepsen >= 0.3.0
JEPSEN_VERSION="${JEPSEN_VERSION:-0.2.4}"
JEPSEN_ACTIVE_NODES_NO=5
CONTROL_LEIN_RUN_ARGS="test-all --node-configs resources/node-config.edn"
CONTROL_LEIN_RUN_STDOUT_LOGS=1
@@ -22,7 +24,7 @@ PRINT_CONTEXT() {
HELP_EXIT() {
echo ""
echo "HELP: $0 help|cluster-up|cluster-refresh|cluster-nodes-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]"
echo "HELP: $0 help|cluster-up|cluster-refresh|cluster-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]"
echo ""
echo " test args --binary MEMGRAPH_BINARY_PATH"
echo " --ignore-run-stdout-logs Ignore lein run stdout logs."
@@ -41,18 +43,24 @@ INFO() {
/bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m" "$@"
}
if [[ "$#" -lt 1 || "$1" == "-h" || "$1" == "--help" ]]; then
HELP_EXIT
fi
if ! command -v docker > /dev/null 2>&1 || ! command -v docker-compose > /dev/null 2>&1; then
ERROR "docker and docker-compose have to be installed."
exit 1
fi
if [ ! -d "$script_dir/jepsen" ]; then
# TODO(deda): install apt get docker-compose-plugin on all build machines.
git clone https://github.com/jepsen-io/jepsen.git -b "$JEPSEN_VERSION" "$script_dir/jepsen"
if [ "$JEPSEN_VERSION" == "v0.3.0" ]; then
if [ -f "$script_dir/jepsen_0.3.0.patch" ]; then
cd "$script_dir/jepsen"
git apply "$script_dir/jepsen_0.3.0.patch"
cd "$script_dir"
fi
fi
fi
if [ "$#" -lt 1 ]; then
HELP_EXIT
fi
PROCESS_ARGS() {
@@ -191,7 +199,7 @@ CLUSTER_UP() {
}
CLUSTER_DEALLOC() {
ps=$(docker ps -a --filter name=jepsen* -q)
ps=$(docker ps --filter name=jepsen* --filter status=running -q)
if [[ ! -z ${ps} ]]; then
echo "Killing ${ps}"
docker rm -f ${ps}
@@ -205,7 +213,6 @@ CLUSTER_DEALLOC() {
else
echo "No Jepsen containers detected!"
fi
echo "Cluster dealloc DONE"
}
# Initialize testing context by copying source/binary files. Inside CI,
@@ -232,7 +239,7 @@ case $1 in
CLUSTER_DEALLOC
;;
cluster-nodes-cleanup)
cluster-cleanup)
jepsen_control_exec="docker exec jepsen-control bash -c"
INFO "Deleting /jepsen/memgraph/store/* on jepsen-control"
$jepsen_control_exec "rm -rf /jepsen/memgraph/store/*"
@@ -245,13 +252,9 @@ case $1 in
;;
mgbuild)
PROCESS_ARGS "$@"
PRINT_CONTEXT
# docker cp -L mgbuild_debian-12:/memgraph/build/memgraph "${MEMGRAPH_BUILD_PATH}/"
# NOTE: mgconsole is interesting inside jepsen container to inspect Memgraph state.
# docker cp -L mgbuild_debian-12:/usr/local/bin/mgconsole "${MEMGRAPH_BUILD_PATH}/"
echo ""
echo "TODO(gitbuda): Build memgraph for Jepsen (on v0.3.5 for Debian 12) via memgraph/memgraph-builder"
echo "TODO(gitbuda): Build memgraph for Debian 10 via memgraph/memgraph-builder"
exit 1
;;

View File

@@ -25,7 +25,6 @@
:--storage-recover-on-startup
:--storage-wal-enabled
:--storage-snapshot-interval-sec 300
:--replication-restore-state-on-startup
:--storage-properties-on-edges))
(defn stop-node!