Compare commits
3 Commits
T0850-MG-s
...
T0074-Repl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c553112b92 | ||
|
|
41635e7306 | ||
|
|
ed4c4e6823 |
@@ -24,6 +24,14 @@ for file in $modified_files; do
|
||||
|
||||
git checkout-index --prefix="$tmpdir/" -- $file
|
||||
|
||||
echo "Running clang-format..."
|
||||
$project_folder/tools/git-clang-format $tmpdir/$file
|
||||
CODE=$?
|
||||
|
||||
if [ $CODE -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
# Do not break header checker
|
||||
echo "Running header checker..."
|
||||
$project_folder/tools/header-checker.py $tmpdir/$file $file --amend-year
|
||||
@@ -31,6 +39,7 @@ for file in $modified_files; do
|
||||
if [ $CODE -ne 0 ]; then
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
done;
|
||||
|
||||
return ${FAIL}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: # arguments to configure black
|
||||
- --line-length=120
|
||||
- --include='\.pyi?$'
|
||||
# these folders wont be formatted by black
|
||||
- --exclude="""\.git |
|
||||
\.__pycache__|
|
||||
build|
|
||||
libs|
|
||||
.cache"""
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v13.0.0
|
||||
hooks:
|
||||
- id: clang-format
|
||||
@@ -184,8 +184,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \
|
||||
-Werror=switch -Werror=switch-bool -Werror=return-type \
|
||||
-Werror=return-stack-address \
|
||||
-Wno-c99-designator \
|
||||
-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT")
|
||||
-Wno-c99-designator")
|
||||
|
||||
# Don't omit frame pointer in RelWithDebInfo, for additional callchain debug.
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
|
||||
@@ -47,14 +47,6 @@ modifications:
|
||||
value: ""
|
||||
override: false
|
||||
|
||||
- name: "bolt_cert_file"
|
||||
value: "/etc/memgraph/ssl/cert.pem"
|
||||
override: false
|
||||
|
||||
- name: "bolt_key_file"
|
||||
value: "/etc/memgraph/ssl/key.pem"
|
||||
override: false
|
||||
|
||||
- name: "storage_properties_on_edges"
|
||||
value: "true"
|
||||
override: true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1179,15 +1179,16 @@ def read_proc(func: typing.Callable[..., Record]):
|
||||
"""
|
||||
Register `func` as a read-only procedure of the current module.
|
||||
|
||||
The decorator `read_proc` is meant to be used to register module procedures.
|
||||
The registered `func` needs to be a callable which optionally takes
|
||||
`ProcCtx` as its first argument. Other arguments of `func` will be bound to
|
||||
values passed in the cypherQuery. The full signature of `func` needs to be
|
||||
annotated with types. The return type must be `Record(field_name=type, ...)`
|
||||
and the procedure must produce either a complete Record or None. To mark a
|
||||
field as deprecated, use `Record(field_name=Deprecated(type), ...)`.
|
||||
Multiple records can be produced by returning an iterable of them.
|
||||
Registering generator functions is currently not supported.
|
||||
`read_proc` is meant to be used as a decorator function to register module
|
||||
procedures. The registered `func` needs to be a callable which optionally
|
||||
takes `ProcCtx` as the first argument. Other arguments of `func` will be
|
||||
bound to values passed in the cypherQuery. The full signature of `func`
|
||||
needs to be annotated with types. The return type must be
|
||||
`Record(field_name=type, ...)` and the procedure must produce either a
|
||||
complete Record or None. To mark a field as deprecated, use
|
||||
`Record(field_name=Deprecated(type), ...)`. Multiple records can be
|
||||
produced by returning an iterable of them. Registering generator functions
|
||||
is currently not supported.
|
||||
|
||||
Example usage.
|
||||
|
||||
@@ -1221,16 +1222,16 @@ def write_proc(func: typing.Callable[..., Record]):
|
||||
"""
|
||||
Register `func` as a writeable procedure of the current module.
|
||||
|
||||
The decorator `write_proc` is meant to be used to register module
|
||||
`write_proc` is meant to be used as a decorator function to register module
|
||||
procedures. The registered `func` needs to be a callable which optionally
|
||||
takes `ProcCtx` as the first argument. Other arguments of `func` will be
|
||||
bound to values passed in the cypherQuery. The full signature of `func`
|
||||
needs to be annotated with types. The return type must be
|
||||
`Record(field_name=type, ...)` and the procedure must produce either a
|
||||
complete Record or None. To mark a field as deprecated, use
|
||||
`Record(field_name=Deprecated(type), ...)`. Multiple records can be produced
|
||||
by returning an iterable of them. Registering generator functions is
|
||||
currently not supported.
|
||||
`Record(field_name=Deprecated(type), ...)`. Multiple records can be
|
||||
produced by returning an iterable of them. Registering generator functions
|
||||
is currently not supported.
|
||||
|
||||
Example usage.
|
||||
|
||||
@@ -1458,9 +1459,8 @@ def transformation(func: typing.Callable[..., Record]):
|
||||
class FuncCtx:
|
||||
"""Context of a function being executed.
|
||||
|
||||
Access to a FuncCtx is only valid during a single execution of a function in
|
||||
a query. You should not globally store a FuncCtx instance. The graph object
|
||||
within the FuncCtx is not mutable.
|
||||
Access to a FuncCtx is only valid during a single execution of a transformation.
|
||||
You should not globally store a FuncCtx instance.
|
||||
"""
|
||||
|
||||
__slots__ = "_graph"
|
||||
@@ -1475,45 +1475,6 @@ class FuncCtx:
|
||||
|
||||
|
||||
def function(func: typing.Callable):
|
||||
"""
|
||||
Register `func` as a user-defined function in the current module.
|
||||
|
||||
The decorator `function` is meant to be used to register module functions.
|
||||
The registered `func` needs to be a callable which optionally takes
|
||||
`FuncCtx` as its first argument. Other arguments of `func` will be bound to
|
||||
values passed in the Cypher query. Only the funcion arguments need to be
|
||||
annotated with types. The return type doesn't need to be specified, but it
|
||||
has to be supported by `mgp.Any`. Registering generator functions is
|
||||
currently not supported.
|
||||
|
||||
Example usage.
|
||||
|
||||
```
|
||||
import mgp
|
||||
@mgp.function
|
||||
def func_example(context: mgp.FuncCtx,
|
||||
required_arg: str,
|
||||
optional_arg: mgp.Nullable[str] = None
|
||||
):
|
||||
return_args = [required_arg]
|
||||
if optional_arg is not None:
|
||||
return_args.append(optional_arg)
|
||||
# Return any kind of result supported by mgp.Any
|
||||
return return_args
|
||||
```
|
||||
|
||||
The example function above returns a list of provided arguments:
|
||||
* `required_arg` is always present and its value is the first argument of
|
||||
the function.
|
||||
* `optional_arg` is present if the second argument of the function is not
|
||||
`null`.
|
||||
Any errors can be reported by raising an Exception.
|
||||
|
||||
The function can be invoked in Cypher using the following calls:
|
||||
RETURN example.func_example("first argument", "second_argument");
|
||||
RETURN example.func_example("first argument");
|
||||
Naturally, you may pass in different arguments.
|
||||
"""
|
||||
raise_if_does_not_meet_requirements(func)
|
||||
register_func = _mgp.Module.add_function
|
||||
sig = inspect.signature(func)
|
||||
|
||||
22
init
22
init
@@ -111,22 +111,10 @@ if [[ "$setup_libs" == "true" ]]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# setup gql_behave dependencies
|
||||
setup_virtualenv tests/gql_behave
|
||||
|
||||
# setup stress dependencies
|
||||
setup_virtualenv tests/stress
|
||||
|
||||
# setup integration/ldap dependencies
|
||||
setup_virtualenv tests/integration/ldap
|
||||
|
||||
# Setup tests dependencies.
|
||||
# cd tests
|
||||
# ./setup.sh
|
||||
# cd ..
|
||||
# TODO(gitbuda): Remove setup_virtualenv, replace it with tests/ve3. Take care
|
||||
# of the build order because tests/setup.py builds pymgclient which depends on
|
||||
# mgclient which is build after this script by calling make.
|
||||
cd tests
|
||||
./setup.sh
|
||||
cd ..
|
||||
|
||||
echo "Done installing dependencies for Memgraph"
|
||||
|
||||
@@ -135,7 +123,3 @@ for hook in $(find $DIR/.githooks -type f -printf "%f\n"); do
|
||||
ln -s -f "$DIR/.githooks/$hook" "$DIR/.git/hooks/$hook"
|
||||
echo "Added $hook hook"
|
||||
done;
|
||||
|
||||
# Install precommit hook
|
||||
python3 -m pip install pre-commit
|
||||
python3 -m pre_commit install
|
||||
|
||||
@@ -122,6 +122,7 @@ declare -A primary_urls=(
|
||||
["protobuf"]="http://$local_cache_host/git/protobuf.git"
|
||||
["pulsar"]="http://$local_cache_host/git/pulsar.git"
|
||||
["librdtsc"]="http://$local_cache_host/git/librdtsc.git"
|
||||
["gqlalchemy"]="http://$local_cache_host/git/gqlalchemy.git"
|
||||
)
|
||||
|
||||
# The goal of secondary urls is to have links to the "source of truth" of
|
||||
@@ -147,6 +148,7 @@ declare -A secondary_urls=(
|
||||
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
|
||||
["pulsar"]="https://github.com/apache/pulsar.git"
|
||||
["librdtsc"]="https://github.com/gabrieleara/librdtsc.git"
|
||||
["gqlalchemy"]="http://github.com/memgraph/gqlalchemy.git"
|
||||
)
|
||||
|
||||
# antlr
|
||||
@@ -199,7 +201,7 @@ git apply ../rocksdb.patch
|
||||
popd
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="96e95c6845463cbe88948392be58d26da0d5ffd3" # (2022-02-08)
|
||||
mgclient_tag="v1.3.0" # (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
|
||||
|
||||
@@ -238,3 +240,10 @@ repo_clone_try_double "${primary_urls[librdtsc]}" "${secondary_urls[librdtsc]}"
|
||||
pushd librdtsc
|
||||
git apply ../librdtsc.patch
|
||||
popd
|
||||
|
||||
#gqlalchemy
|
||||
gqlalchemy_tag="v1.2.0"
|
||||
repo_clone_try_double "${primary_urls[gqlalchemy]}" "${secondary_urls[gqlalchemy]}" "gqlalchemy" "$gqlalchemy_tag" true
|
||||
pushd gqlalchemy
|
||||
git apply ../gqlalchemy.patch
|
||||
popd
|
||||
|
||||
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
|
||||
3. using the Licensed Work to create a work or solution
|
||||
which competes (or might reasonably be expected to
|
||||
compete) with the Licensed Work.
|
||||
CHANGE DATE: 2026-27-04
|
||||
CHANGE DATE: 2026-18-02
|
||||
CHANGE LICENSE: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||
|
||||
@@ -41,7 +41,7 @@ set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
|
||||
applications driver by real-time connected data.")
|
||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||
# We also depend on `python3` because we embed it in Memgraph.
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl (>= 1.1.0), python3 (>= 3.5.0), libstdc++6")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl (>= 1.1.0), python3 (>= 3.5.0)")
|
||||
|
||||
# Setting arhitecture extension for rpm packages
|
||||
set(MG_ARCH_EXTENSION_RPM "noarch")
|
||||
@@ -67,7 +67,7 @@ It aims to deliver developers the speed, simplicity and scale required to build
|
||||
the next generation of applications driver by real-time connected data.")
|
||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||
# We also depend on `python3` because we embed it in Memgraph.
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc >= 6")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0")
|
||||
|
||||
# All variables must be set before including.
|
||||
include(CPack)
|
||||
|
||||
@@ -37,7 +37,7 @@ const std::vector<Permission> kPermissionsAll = {
|
||||
Permission::CONSTRAINT, Permission::DUMP, Permission::AUTH, Permission::REPLICATION,
|
||||
Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
|
||||
Permission::CONFIG, Permission::STREAM, Permission::MODULE_READ, Permission::MODULE_WRITE,
|
||||
Permission::WEBSOCKET, Permission::SCHEMA};
|
||||
Permission::WEBSOCKET};
|
||||
} // namespace
|
||||
|
||||
std::string PermissionToString(Permission permission) {
|
||||
@@ -84,8 +84,6 @@ std::string PermissionToString(Permission permission) {
|
||||
return "MODULE_WRITE";
|
||||
case Permission::WEBSOCKET:
|
||||
return "WEBSOCKET";
|
||||
case Permission::SCHEMA:
|
||||
return "SCHEMA";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ enum class Permission : uint64_t {
|
||||
STREAM = 1U << 17U,
|
||||
MODULE_READ = 1U << 18U,
|
||||
MODULE_WRITE = 1U << 19U,
|
||||
WEBSOCKET = 1U << 20U,
|
||||
SCHEMA = 1U << 21U
|
||||
WEBSOCKET = 1U << 20U
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -1,19 +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 <cstdint>
|
||||
|
||||
namespace memgraph::common {
|
||||
enum class SchemaType : uint8_t { BOOL, INT, STRING, DATE, LOCALTIME, LOCALDATETIME, DURATION };
|
||||
|
||||
} // namespace memgraph::common
|
||||
@@ -78,18 +78,14 @@ bool ClientContext::use_ssl() { return use_ssl_; }
|
||||
|
||||
ServerContext::ServerContext(const std::string &key_file, const std::string &cert_file, const std::string &ca_file,
|
||||
bool verify_peer) {
|
||||
namespace ssl = boost::asio::ssl;
|
||||
ctx_.emplace(ssl::context::tls_server);
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ctx_->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::no_sslv3 |
|
||||
ssl::context::single_dh_use);
|
||||
ctx_.emplace(boost::asio::ssl::context::tls_server);
|
||||
ctx_->set_default_verify_paths();
|
||||
// TODO: add support for encrypted private keys
|
||||
// TODO: add certificate revocation list (CRL)
|
||||
boost::system::error_code ec;
|
||||
ctx_->use_certificate_chain_file(cert_file, ec);
|
||||
MG_ASSERT(!ec, "Couldn't load server certificate from file: {}", cert_file);
|
||||
ctx_->use_private_key_file(key_file, ssl::context::pem, ec);
|
||||
ctx_->use_private_key_file(key_file, boost::asio::ssl::context::pem, ec);
|
||||
MG_ASSERT(!ec, "Couldn't load server private key from file: {}", key_file);
|
||||
|
||||
ctx_->set_options(SSL_OP_NO_SSLv3, ec);
|
||||
@@ -104,7 +100,7 @@ ServerContext::ServerContext(const std::string &key_file, const std::string &cer
|
||||
if (verify_peer) {
|
||||
// Enable verification of the client certificate.
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ctx_->set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert, ec);
|
||||
ctx_->set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert, ec);
|
||||
MG_ASSERT(!ec, "Setting SSL verification mode failed!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,135 +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 <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/v2/pool.hpp"
|
||||
#include "communication/v2/session.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::communication::v2 {
|
||||
|
||||
template <class TSession, class TSessionData>
|
||||
class Listener final : public std::enable_shared_from_this<Listener<TSession, TSessionData>> {
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
using SessionHandler = Session<TSession, TSessionData>;
|
||||
using std::enable_shared_from_this<Listener<TSession, TSessionData>>::shared_from_this;
|
||||
|
||||
public:
|
||||
Listener(const Listener &) = delete;
|
||||
Listener(Listener &&) = delete;
|
||||
Listener &operator=(const Listener &) = delete;
|
||||
Listener &operator=(Listener &&) = delete;
|
||||
~Listener() {}
|
||||
|
||||
template <typename... Args>
|
||||
static std::shared_ptr<Listener> Create(Args &&...args) {
|
||||
return std::shared_ptr<Listener>{new Listener(std::forward<Args>(args)...)};
|
||||
}
|
||||
|
||||
void Start() { DoAccept(); }
|
||||
|
||||
bool IsRunning() const noexcept { return alive_.load(std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
Listener(boost::asio::io_context &io_context, TSessionData *data, ServerContext *server_context,
|
||||
tcp::endpoint &endpoint, const std::string_view service_name, const uint64_t inactivity_timeout_sec)
|
||||
: io_context_(io_context),
|
||||
data_(data),
|
||||
server_context_(server_context),
|
||||
acceptor_(io_context_),
|
||||
endpoint_{endpoint},
|
||||
service_name_{service_name},
|
||||
inactivity_timeout_{inactivity_timeout_sec} {
|
||||
boost::system::error_code ec;
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (ec) {
|
||||
OnError(ec, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
|
||||
if (ec) {
|
||||
OnError(ec, "set_option");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (ec) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Cannot bind to socket on endpoint {}.", endpoint, "https://memgr.ph/socket"));
|
||||
OnError(ec, "bind");
|
||||
return;
|
||||
}
|
||||
|
||||
acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
|
||||
if (ec) {
|
||||
OnError(ec, "listen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DoAccept() {
|
||||
acceptor_.async_accept(io_context_,
|
||||
[shared_this = shared_from_this()](auto ec, boost::asio::ip::tcp::socket &&socket) {
|
||||
shared_this->OnAccept(ec, std::move(socket));
|
||||
});
|
||||
}
|
||||
|
||||
void OnAccept(boost::system::error_code ec, tcp::socket socket) {
|
||||
if (ec) {
|
||||
return OnError(ec, "accept");
|
||||
}
|
||||
|
||||
auto session = SessionHandler::Create(std::move(socket), data_, *server_context_, endpoint_, inactivity_timeout_,
|
||||
service_name_);
|
||||
session->Start();
|
||||
DoAccept();
|
||||
}
|
||||
|
||||
void OnError(const boost::system::error_code &ec, const std::string_view what) {
|
||||
spdlog::error("Listener failed on {}: {}", what, ec.message());
|
||||
alive_.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
boost::asio::io_context &io_context_;
|
||||
TSessionData *data_;
|
||||
ServerContext *server_context_;
|
||||
tcp::acceptor acceptor_;
|
||||
|
||||
tcp::endpoint endpoint_;
|
||||
std::string_view service_name_;
|
||||
std::chrono::seconds inactivity_timeout_;
|
||||
|
||||
std::atomic<bool> alive_;
|
||||
};
|
||||
} // namespace memgraph::communication::v2
|
||||
@@ -1,68 +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 <cstddef>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::communication::v2 {
|
||||
|
||||
class IOContextThreadPool final {
|
||||
private:
|
||||
using IOContext = boost::asio::io_context;
|
||||
using IOContextGuard = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>;
|
||||
|
||||
public:
|
||||
explicit IOContextThreadPool(size_t pool_size) : guard_{io_context_.get_executor()}, pool_size_{pool_size} {
|
||||
MG_ASSERT(pool_size != 0, "Pool size must be greater then 0!");
|
||||
}
|
||||
|
||||
IOContextThreadPool(const IOContextThreadPool &) = delete;
|
||||
IOContextThreadPool &operator=(const IOContextThreadPool &) = delete;
|
||||
IOContextThreadPool(IOContextThreadPool &&) = delete;
|
||||
IOContextThreadPool &operator=(IOContextThreadPool &&) = delete;
|
||||
~IOContextThreadPool() = default;
|
||||
|
||||
void Run() {
|
||||
background_threads_.reserve(pool_size_);
|
||||
for (size_t i = 0; i < pool_size_; ++i) {
|
||||
background_threads_.emplace_back([this]() { io_context_.run(); });
|
||||
}
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
io_context_.stop();
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void AwaitShutdown() { background_threads_.clear(); }
|
||||
|
||||
bool IsRunning() const noexcept { return running_; }
|
||||
|
||||
IOContext &GetIOContext() noexcept { return io_context_; }
|
||||
|
||||
private:
|
||||
/// The pool of io_context.
|
||||
IOContext io_context_;
|
||||
IOContextGuard guard_;
|
||||
size_t pool_size_;
|
||||
std::vector<std::jthread> background_threads_;
|
||||
bool running_{false};
|
||||
};
|
||||
} // namespace memgraph::communication::v2
|
||||
@@ -1,128 +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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/init.hpp"
|
||||
#include "communication/v2/listener.hpp"
|
||||
#include "communication/v2/pool.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
namespace memgraph::communication::v2 {
|
||||
|
||||
using Socket = boost::asio::ip::tcp::socket;
|
||||
using ServerEndpoint = boost::asio::ip::tcp::endpoint;
|
||||
/**
|
||||
* Communication server.
|
||||
*
|
||||
* Listens for incoming connections on the server port and assigns them to the
|
||||
* connection listener. The listener and session are implemented using asio
|
||||
* async model. Currently the implemented model is thread per core model
|
||||
* opposed to io_context per core. The reasoning for opting for the former model
|
||||
* is the robustness to the multiple resource demanding queries that can be split
|
||||
* across multiple threads, and then a single thread would not block io_context,
|
||||
* unlike in the latter model where it is possible that thread that accepts
|
||||
* request is being blocked by demanding query.
|
||||
* All logic is contained within handlers that are being dispatched
|
||||
* on a single strand per session. The only exception is write which is
|
||||
* synchronous since the nature of the clients conenction is synchronous as
|
||||
* well.
|
||||
*
|
||||
* Current Server architecture:
|
||||
* incoming connection -> server -> listener -> session
|
||||
|
||||
*
|
||||
* @tparam TSession the server can handle different Sessions, each session
|
||||
* represents a different protocol so the same network infrastructure
|
||||
* can be used for handling different protocols
|
||||
* @tparam TSessionData the class with objects that will be forwarded to the
|
||||
* session
|
||||
*/
|
||||
template <typename TSession, typename TSessionData>
|
||||
class Server final {
|
||||
using ServerHandler = Server<TSession, TSessionData>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs and binds server to endpoint, operates on session data and
|
||||
* invokes workers_count workers
|
||||
*/
|
||||
Server(ServerEndpoint &endpoint, TSessionData *session_data, ServerContext *server_context,
|
||||
const int inactivity_timeout_sec, const std::string_view service_name,
|
||||
size_t workers_count = std::thread::hardware_concurrency())
|
||||
: endpoint_{endpoint},
|
||||
service_name_{service_name},
|
||||
context_thread_pool_{workers_count},
|
||||
listener_{Listener<TSession, TSessionData>::Create(context_thread_pool_.GetIOContext(), session_data,
|
||||
server_context, endpoint_, service_name_,
|
||||
inactivity_timeout_sec)} {}
|
||||
|
||||
~Server() { MG_ASSERT(!IsRunning(), "Server wasn't shutdown properly"); }
|
||||
|
||||
Server(const Server &) = delete;
|
||||
Server(Server &&) = delete;
|
||||
Server &operator=(const Server &) = delete;
|
||||
Server &operator=(Server &&) = delete;
|
||||
|
||||
const auto &Endpoint() const {
|
||||
MG_ASSERT(IsRunning(), "You can't get the server endpoint when it's not running!");
|
||||
return endpoint_;
|
||||
}
|
||||
|
||||
bool Start() {
|
||||
if (IsRunning()) {
|
||||
spdlog::error("The server is already running");
|
||||
return false;
|
||||
}
|
||||
listener_->Start();
|
||||
|
||||
spdlog::info("{} server is fully armed and operational", service_name_);
|
||||
spdlog::info("{} listening on {}", service_name_, endpoint_.address());
|
||||
context_thread_pool_.Run();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
context_thread_pool_.Shutdown();
|
||||
spdlog::info("{} shutting down...", service_name_);
|
||||
}
|
||||
|
||||
void AwaitShutdown() { context_thread_pool_.AwaitShutdown(); }
|
||||
|
||||
bool IsRunning() const noexcept { return context_thread_pool_.IsRunning() && listener_->IsRunning(); }
|
||||
|
||||
private:
|
||||
ServerEndpoint endpoint_;
|
||||
std::string service_name_;
|
||||
|
||||
IOContextThreadPool context_thread_pool_;
|
||||
std::shared_ptr<Listener<TSession, TSessionData>> listener_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::communication::v2
|
||||
@@ -1,508 +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 <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/socket_base.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/ssl/stream_base.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/system_context.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/beast/websocket/rfc6455.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
namespace memgraph::communication::v2 {
|
||||
|
||||
/**
|
||||
* This is used to provide input to user Sessions. All Sessions used with the
|
||||
* network stack should use this class as their input stream.
|
||||
*/
|
||||
using InputStream = communication::Buffer::ReadEnd;
|
||||
using tcp = boost::asio::ip::tcp;
|
||||
|
||||
/**
|
||||
* This is used to provide output from user Sessions. All Sessions used with the
|
||||
* network stack should use this class for their output stream.
|
||||
*/
|
||||
class OutputStream final {
|
||||
public:
|
||||
explicit OutputStream(std::function<bool(const uint8_t *, size_t, bool)> write_function)
|
||||
: write_function_(write_function) {}
|
||||
|
||||
OutputStream(const OutputStream &) = delete;
|
||||
OutputStream(OutputStream &&) = delete;
|
||||
OutputStream &operator=(const OutputStream &) = delete;
|
||||
OutputStream &operator=(OutputStream &&) = delete;
|
||||
~OutputStream() = default;
|
||||
|
||||
bool Write(const uint8_t *data, size_t len, bool have_more = false) { return write_function_(data, len, have_more); }
|
||||
|
||||
bool Write(const std::string &str, bool have_more = false) {
|
||||
return Write(reinterpret_cast<const uint8_t *>(str.data()), str.size(), have_more);
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<bool(const uint8_t *, size_t, bool)> write_function_;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is used internally in the communication stack to handle all user
|
||||
* Websocket Sessions. It handles socket ownership, inactivity timeout and protocol
|
||||
* wrapping.
|
||||
*/
|
||||
template <typename TSession, typename TSessionData>
|
||||
class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TSession, TSessionData>> {
|
||||
using WebSocket = boost::beast::websocket::stream<boost::beast::tcp_stream>;
|
||||
using std::enable_shared_from_this<WebsocketSession<TSession, TSessionData>>::shared_from_this;
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
static std::shared_ptr<WebsocketSession> Create(Args &&...args) {
|
||||
return std::shared_ptr<WebsocketSession>(new WebsocketSession(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// Start the asynchronous accept operation
|
||||
template <class Body, class Allocator>
|
||||
void DoAccept(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> req) {
|
||||
execution_active_ = true;
|
||||
// Set suggested timeout settings for the websocket
|
||||
ws_.set_option(boost::beast::websocket::stream_base::timeout::suggested(boost::beast::role_type::server));
|
||||
boost::asio::socket_base::keep_alive option(true);
|
||||
|
||||
// Set a decorator to change the Server of the handshake
|
||||
ws_.set_option(boost::beast::websocket::stream_base::decorator([](boost::beast::websocket::response_type &res) {
|
||||
res.set(boost::beast::http::field::server, std::string("Memgraph Bolt WS"));
|
||||
res.set(boost::beast::http::field::sec_websocket_protocol, "binary");
|
||||
}));
|
||||
ws_.binary(true);
|
||||
|
||||
// Accept the websocket handshake
|
||||
ws_.async_accept(
|
||||
req, boost::asio::bind_executor(strand_, std::bind_front(&WebsocketSession::OnAccept, shared_from_this())));
|
||||
}
|
||||
|
||||
bool Write(const uint8_t *data, size_t len) {
|
||||
if (!IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
ws_.write(boost::asio::buffer(data, len), ec);
|
||||
if (ec) {
|
||||
OnError(ec, "write");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Take ownership of the socket
|
||||
explicit WebsocketSession(tcp::socket &&socket, TSessionData *data, tcp::endpoint endpoint,
|
||||
std::string_view service_name)
|
||||
: ws_(std::move(socket)),
|
||||
strand_{boost::asio::make_strand(ws_.get_executor())},
|
||||
output_stream_([this](const uint8_t *data, size_t len, bool /*have_more*/) { return Write(data, len); }),
|
||||
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
|
||||
endpoint_{endpoint},
|
||||
remote_endpoint_{ws_.next_layer().socket().remote_endpoint()},
|
||||
service_name_{service_name} {}
|
||||
|
||||
void OnAccept(boost::beast::error_code ec) {
|
||||
if (ec) {
|
||||
return OnError(ec, "accept");
|
||||
}
|
||||
|
||||
// Read a message
|
||||
DoRead();
|
||||
}
|
||||
|
||||
void DoRead() {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
// Read a message into our buffer
|
||||
auto buffer = input_buffer_.write_end()->Allocate();
|
||||
ws_.async_read_some(
|
||||
boost::asio::buffer(buffer.data, buffer.len),
|
||||
boost::asio::bind_executor(strand_, std::bind_front(&WebsocketSession::OnRead, shared_from_this())));
|
||||
}
|
||||
|
||||
void OnRead(const boost::system::error_code &ec, [[maybe_unused]] const size_t bytes_transferred) {
|
||||
// This indicates that the WebsocketSession was closed
|
||||
if (ec == boost::beast::websocket::error::closed) {
|
||||
return;
|
||||
}
|
||||
if (ec) {
|
||||
OnError(ec, "read");
|
||||
}
|
||||
input_buffer_.write_end()->Written(bytes_transferred);
|
||||
|
||||
try {
|
||||
session_.Execute();
|
||||
DoRead();
|
||||
} catch (const SessionClosedException &e) {
|
||||
spdlog::info("{} client {}:{} closed the connection.", service_name_, remote_endpoint_.address(),
|
||||
remote_endpoint_.port());
|
||||
DoClose();
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error(
|
||||
"Exception was thrown while processing event in {} session "
|
||||
"associated with {}:{}",
|
||||
service_name_, remote_endpoint_.address(), remote_endpoint_.port());
|
||||
spdlog::debug("Exception message: {}", e.what());
|
||||
DoClose();
|
||||
}
|
||||
}
|
||||
|
||||
void OnError(const boost::system::error_code &ec, const std::string_view action) {
|
||||
spdlog::error("Websocket Bolt session error: {} on {}", ec.message(), action);
|
||||
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void DoClose() {
|
||||
ws_.async_close(
|
||||
boost::beast::websocket::close_code::normal,
|
||||
boost::asio::bind_executor(
|
||||
strand_, [shared_this = shared_from_this()](boost::beast::error_code ec) { shared_this->OnClose(ec); }));
|
||||
}
|
||||
|
||||
void OnClose(const boost::system::error_code &ec) {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
if (ec) {
|
||||
return OnError(ec, "close");
|
||||
}
|
||||
}
|
||||
|
||||
bool IsConnected() const { return ws_.is_open() && execution_active_; }
|
||||
|
||||
WebSocket ws_;
|
||||
boost::asio::strand<WebSocket::executor_type> strand_;
|
||||
|
||||
communication::Buffer input_buffer_;
|
||||
OutputStream output_stream_;
|
||||
TSession session_;
|
||||
tcp::endpoint endpoint_;
|
||||
tcp::endpoint remote_endpoint_;
|
||||
std::string_view service_name_;
|
||||
bool execution_active_{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is used internally in the communication stack to handle all user
|
||||
* Sessions. It handles socket ownership, inactivity timeout and protocol
|
||||
* wrapping.
|
||||
*/
|
||||
template <typename TSession, typename TSessionData>
|
||||
class Session final : public std::enable_shared_from_this<Session<TSession, TSessionData>> {
|
||||
using TCPSocket = tcp::socket;
|
||||
using SSLSocket = boost::asio::ssl::stream<TCPSocket>;
|
||||
using std::enable_shared_from_this<Session<TSession, TSessionData>>::shared_from_this;
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
static std::shared_ptr<Session> Create(Args &&...args) {
|
||||
return std::shared_ptr<Session>(new Session(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
Session(const Session &) = delete;
|
||||
Session(Session &&) = delete;
|
||||
Session &operator=(const Session &) = delete;
|
||||
Session &operator=(Session &&) = delete;
|
||||
~Session() = default;
|
||||
|
||||
bool Start() {
|
||||
if (execution_active_) {
|
||||
return false;
|
||||
}
|
||||
execution_active_ = true;
|
||||
timeout_timer_.async_wait(boost::asio::bind_executor(strand_, std::bind(&Session::OnTimeout, shared_from_this())));
|
||||
|
||||
if (std::holds_alternative<SSLSocket>(socket_)) {
|
||||
boost::asio::dispatch(strand_, [shared_this = shared_from_this()] { shared_this->DoHandshake(); });
|
||||
} else {
|
||||
boost::asio::dispatch(strand_, [shared_this = shared_from_this()] { shared_this->DoRead(); });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Write(const uint8_t *data, size_t len, bool have_more = false) {
|
||||
if (!IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
return std::visit(
|
||||
utils::Overloaded{[shared_this = shared_from_this(), data, len, have_more](TCPSocket &socket) mutable {
|
||||
boost::system::error_code ec;
|
||||
while (len > 0) {
|
||||
const auto sent = socket.send(boost::asio::buffer(data, len),
|
||||
MSG_NOSIGNAL | (have_more ? MSG_MORE : 0), ec);
|
||||
if (ec) {
|
||||
shared_this->OnError(ec);
|
||||
return false;
|
||||
}
|
||||
data += sent;
|
||||
len -= sent;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[shared_this = shared_from_this(), data, len](SSLSocket &socket) mutable {
|
||||
boost::system::error_code ec;
|
||||
while (len > 0) {
|
||||
const auto sent = socket.write_some(boost::asio::buffer(data, len), ec);
|
||||
if (ec) {
|
||||
shared_this->OnError(ec);
|
||||
return false;
|
||||
}
|
||||
data += sent;
|
||||
len -= sent;
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
socket_);
|
||||
}
|
||||
|
||||
bool IsConnected() const {
|
||||
return std::visit([this](const auto &socket) { return execution_active_ && socket.lowest_layer().is_open(); },
|
||||
socket_);
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Session(tcp::socket &&socket, TSessionData *data, ServerContext &server_context, tcp::endpoint endpoint,
|
||||
const std::chrono::seconds inactivity_timeout_sec, std::string_view service_name)
|
||||
: socket_(CreateSocket(std::move(socket), server_context)),
|
||||
strand_{boost::asio::make_strand(GetExecutor())},
|
||||
output_stream_([this](const uint8_t *data, size_t len, bool have_more) { return Write(data, len, have_more); }),
|
||||
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
|
||||
data_{data},
|
||||
endpoint_{endpoint},
|
||||
remote_endpoint_{GetRemoteEndpoint()},
|
||||
service_name_{service_name},
|
||||
timeout_seconds_(inactivity_timeout_sec),
|
||||
timeout_timer_(GetExecutor()) {
|
||||
ExecuteForSocket([](auto &&socket) {
|
||||
socket.lowest_layer().set_option(tcp::no_delay(true)); // enable PSH
|
||||
socket.lowest_layer().set_option(boost::asio::socket_base::keep_alive(true)); // enable SO_KEEPALIVE
|
||||
socket.lowest_layer().non_blocking(false);
|
||||
});
|
||||
timeout_timer_.expires_at(boost::asio::steady_timer::time_point::max());
|
||||
spdlog::info("Accepted a connection from {}:", service_name_, remote_endpoint_.address(), remote_endpoint_.port());
|
||||
}
|
||||
|
||||
void DoRead() {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
timeout_timer_.expires_after(timeout_seconds_);
|
||||
ExecuteForSocket([this](auto &&socket) {
|
||||
auto buffer = input_buffer_.write_end()->Allocate();
|
||||
socket.async_read_some(
|
||||
boost::asio::buffer(buffer.data, buffer.len),
|
||||
boost::asio::bind_executor(strand_, std::bind_front(&Session::OnRead, shared_from_this())));
|
||||
});
|
||||
}
|
||||
|
||||
bool IsWebsocketUpgrade(boost::beast::http::request_parser<boost::beast::http::string_body> &parser) {
|
||||
boost::system::error_code error_code_parsing;
|
||||
parser.put(boost::asio::buffer(input_buffer_.read_end()->data(), input_buffer_.read_end()->size()),
|
||||
error_code_parsing);
|
||||
if (error_code_parsing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return boost::beast::websocket::is_upgrade(parser.get());
|
||||
}
|
||||
|
||||
void OnRead(const boost::system::error_code &ec, const size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
return OnError(ec);
|
||||
}
|
||||
input_buffer_.write_end()->Written(bytes_transferred);
|
||||
|
||||
// Can be a websocket connection only on the first read, since it is not
|
||||
// expected from clients to upgrade from tcp to websocket
|
||||
if (!has_received_msg_) {
|
||||
has_received_msg_ = true;
|
||||
boost::beast::http::request_parser<boost::beast::http::string_body> parser;
|
||||
|
||||
if (IsWebsocketUpgrade(parser)) {
|
||||
spdlog::info("Switching {} to websocket connection", remote_endpoint_);
|
||||
if (std::holds_alternative<TCPSocket>(socket_)) {
|
||||
auto sock = std::get<TCPSocket>(std::move(socket_));
|
||||
WebsocketSession<TSession, TSessionData>::Create(std::move(sock), data_, endpoint_, service_name_)
|
||||
->DoAccept(parser.release());
|
||||
execution_active_ = false;
|
||||
return;
|
||||
}
|
||||
spdlog::error("Error while upgrading connection to websocket");
|
||||
DoShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
session_.Execute();
|
||||
DoRead();
|
||||
} catch (const SessionClosedException &e) {
|
||||
spdlog::info("{} client {}:{} closed the connection.", service_name_, remote_endpoint_.address(),
|
||||
remote_endpoint_.port());
|
||||
DoShutdown();
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error(
|
||||
"Exception was thrown while processing event in {} session "
|
||||
"associated with {}:{}",
|
||||
service_name_, remote_endpoint_.address(), remote_endpoint_.port());
|
||||
spdlog::debug("Exception message: {}", e.what());
|
||||
DoShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void OnError(const boost::system::error_code &ec) {
|
||||
if (ec == boost::asio::error::operation_aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec == boost::asio::error::eof) {
|
||||
spdlog::info("Session closed by peer");
|
||||
} else {
|
||||
spdlog::error("Session error: {}", ec.message());
|
||||
}
|
||||
|
||||
DoShutdown();
|
||||
}
|
||||
|
||||
void DoShutdown() {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
execution_active_ = false;
|
||||
timeout_timer_.cancel();
|
||||
ExecuteForSocket([](auto &socket) {
|
||||
boost::system::error_code ec;
|
||||
auto &lowest_layer = socket.lowest_layer();
|
||||
lowest_layer.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
|
||||
if (ec) {
|
||||
spdlog::error("Session shutdown failed: {}", ec.what());
|
||||
}
|
||||
lowest_layer.close();
|
||||
});
|
||||
}
|
||||
|
||||
void DoHandshake() {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
if (auto *socket = std::get_if<SSLSocket>(&socket_); socket) {
|
||||
socket->async_handshake(
|
||||
boost::asio::ssl::stream_base::server,
|
||||
boost::asio::bind_executor(strand_, std::bind_front(&Session::OnHandshake, shared_from_this())));
|
||||
}
|
||||
}
|
||||
|
||||
void OnHandshake(const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
return OnError(ec);
|
||||
}
|
||||
DoRead();
|
||||
}
|
||||
|
||||
void OnClose(const boost::system::error_code &ec) {
|
||||
if (ec) {
|
||||
return OnError(ec);
|
||||
}
|
||||
}
|
||||
|
||||
void OnTimeout() {
|
||||
if (!IsConnected()) {
|
||||
return;
|
||||
}
|
||||
// Check whether the deadline has passed. We compare the deadline against
|
||||
// the current time since a new asynchronous operation may have moved the
|
||||
// deadline before this actor had a chance to run.
|
||||
if (timeout_timer_.expiry() <= boost::asio::steady_timer::clock_type::now()) {
|
||||
// The deadline has passed. Stop the session. The other actors will
|
||||
// terminate as soon as possible.
|
||||
spdlog::info("Shutting down session after {} of inactivity", timeout_seconds_);
|
||||
DoShutdown();
|
||||
} else {
|
||||
// Put the actor back to sleep.
|
||||
timeout_timer_.async_wait(
|
||||
boost::asio::bind_executor(strand_, std::bind(&Session::OnTimeout, shared_from_this())));
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<TCPSocket, SSLSocket> CreateSocket(tcp::socket &&socket, ServerContext &context) {
|
||||
if (context.use_ssl()) {
|
||||
ssl_context_.emplace(context.context_clone());
|
||||
return SSLSocket{std::move(socket), *ssl_context_};
|
||||
}
|
||||
|
||||
return TCPSocket{std::move(socket)};
|
||||
}
|
||||
|
||||
auto GetExecutor() {
|
||||
return std::visit(utils::Overloaded{[](auto &&socket) { return socket.get_executor(); }}, socket_);
|
||||
}
|
||||
|
||||
auto GetRemoteEndpoint() const {
|
||||
return std::visit(utils::Overloaded{[](const auto &socket) { return socket.lowest_layer().remote_endpoint(); }},
|
||||
socket_);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
decltype(auto) ExecuteForSocket(F &&fun) {
|
||||
return std::visit(utils::Overloaded{std::forward<F>(fun)}, socket_);
|
||||
}
|
||||
|
||||
std::variant<TCPSocket, SSLSocket> socket_;
|
||||
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ssl_context_;
|
||||
boost::asio::strand<tcp::socket::executor_type> strand_;
|
||||
|
||||
communication::Buffer input_buffer_;
|
||||
OutputStream output_stream_;
|
||||
TSession session_;
|
||||
TSessionData *data_;
|
||||
tcp::endpoint endpoint_;
|
||||
tcp::endpoint remote_endpoint_;
|
||||
std::string_view service_name_;
|
||||
std::chrono::seconds timeout_seconds_;
|
||||
boost::asio::steady_timer timeout_timer_;
|
||||
bool execution_active_{false};
|
||||
bool has_received_msg_{false};
|
||||
};
|
||||
} // namespace memgraph::communication::v2
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@@ -57,8 +57,6 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
||||
return auth::Permission::MODULE_WRITE;
|
||||
case query::AuthQuery::Privilege::WEBSOCKET:
|
||||
return auth::Permission::WEBSOCKET;
|
||||
case query::AuthQuery::Privilege::SCHEMA:
|
||||
return auth::Permission::SCHEMA;
|
||||
}
|
||||
}
|
||||
} // namespace memgraph::glue
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
#include "communication/bolt/v1/exceptions.hpp"
|
||||
#include "communication/bolt/v1/session.hpp"
|
||||
#include "communication/init.hpp"
|
||||
#include "communication/v2/server.hpp"
|
||||
#include "communication/v2/session.hpp"
|
||||
#include "communication/server.hpp"
|
||||
#include "communication/session.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
@@ -842,14 +842,13 @@ class AuthChecker final : public memgraph::query::AuthChecker {
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
|
||||
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
memgraph::communication::v2::OutputStream> {
|
||||
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::InputStream,
|
||||
memgraph::communication::OutputStream> {
|
||||
public:
|
||||
BoltSession(SessionData *data, const memgraph::communication::v2::ServerEndpoint &endpoint,
|
||||
memgraph::communication::v2::InputStream *input_stream,
|
||||
memgraph::communication::v2::OutputStream *output_stream)
|
||||
: memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
memgraph::communication::v2::OutputStream>(input_stream, output_stream),
|
||||
BoltSession(SessionData *data, const memgraph::io::network::Endpoint &endpoint,
|
||||
memgraph::communication::InputStream *input_stream, memgraph::communication::OutputStream *output_stream)
|
||||
: memgraph::communication::bolt::Session<memgraph::communication::InputStream,
|
||||
memgraph::communication::OutputStream>(input_stream, output_stream),
|
||||
db_(data->db),
|
||||
interpreter_(data->interpreter_context),
|
||||
auth_(data->auth),
|
||||
@@ -859,8 +858,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
endpoint_(endpoint) {
|
||||
}
|
||||
|
||||
using memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
memgraph::communication::v2::OutputStream>::TEncoder;
|
||||
using memgraph::communication::bolt::Session<memgraph::communication::InputStream,
|
||||
memgraph::communication::OutputStream>::TEncoder;
|
||||
|
||||
void BeginTransaction() override { interpreter_.BeginTransaction(); }
|
||||
|
||||
@@ -878,8 +877,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
|
||||
audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
|
||||
memgraph::storage::PropertyValue(params_pv));
|
||||
audit_log_->Record(endpoint_.address, user_ ? *username : "", query, memgraph::storage::PropertyValue(params_pv));
|
||||
}
|
||||
#endif
|
||||
try {
|
||||
@@ -998,10 +996,10 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::audit::Log *audit_log_;
|
||||
#endif
|
||||
memgraph::communication::v2::ServerEndpoint endpoint_;
|
||||
memgraph::io::network::Endpoint endpoint_;
|
||||
};
|
||||
|
||||
using ServerT = memgraph::communication::v2::Server<BoltSession, SessionData>;
|
||||
using ServerT = memgraph::communication::Server<BoltSession, SessionData>;
|
||||
using memgraph::communication::ServerContext;
|
||||
|
||||
// Needed to correctly handle memgraph destruction from a signal handler.
|
||||
@@ -1070,22 +1068,6 @@ int main(int argc, char **argv) {
|
||||
if (maybe_exc) {
|
||||
spdlog::error(memgraph::utils::MessageWithLink("Unable to load support for embedded Python: {}.", *maybe_exc,
|
||||
"https://memgr.ph/python"));
|
||||
} else {
|
||||
// Change how we load dynamic libraries on Python by using RTLD_NOW and
|
||||
// RTLD_DEEPBIND flags. This solves an issue with using the wrong version of
|
||||
// libstd.
|
||||
auto gil = memgraph::py::EnsureGIL();
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
auto *flag = PyLong_FromLong(RTLD_NOW | RTLD_DEEPBIND);
|
||||
auto *setdl = PySys_GetObject("setdlopenflags");
|
||||
MG_ASSERT(setdl);
|
||||
auto *arg = PyTuple_New(1);
|
||||
MG_ASSERT(arg);
|
||||
MG_ASSERT(PyTuple_SetItem(arg, 0, flag) == 0);
|
||||
PyObject_CallObject(setdl, arg);
|
||||
Py_DECREF(flag);
|
||||
Py_DECREF(setdl);
|
||||
Py_DECREF(arg);
|
||||
}
|
||||
} else {
|
||||
spdlog::error(
|
||||
@@ -1259,10 +1241,8 @@ int main(int argc, char **argv) {
|
||||
memgraph::utils::MessageWithLink("Using non-secure Bolt connection (without SSL).", "https://memgr.ph/ssl"));
|
||||
}
|
||||
|
||||
auto server_endpoint = memgraph::communication::v2::ServerEndpoint{
|
||||
boost::asio::ip::address::from_string(FLAGS_bolt_address), static_cast<uint16_t>(FLAGS_bolt_port)};
|
||||
ServerT server(server_endpoint, &session_data, &context, FLAGS_bolt_session_inactivity_timeout, service_name,
|
||||
FLAGS_bolt_num_workers);
|
||||
ServerT server({FLAGS_bolt_address, static_cast<uint16_t>(FLAGS_bolt_port)}, &session_data, &context,
|
||||
FLAGS_bolt_session_inactivity_timeout, service_name, FLAGS_bolt_num_workers);
|
||||
|
||||
// Setup telemetry
|
||||
std::optional<memgraph::telemetry::Telemetry> telemetry;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "query/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/frontend/semantic/symbol.hpp"
|
||||
#include "query/interpret/awesome_memgraph_functions.hpp"
|
||||
@@ -2254,7 +2253,7 @@ cpp<#
|
||||
(lcp:define-enum privilege
|
||||
(create delete match merge set remove index stats auth constraint
|
||||
dump replication durability read_file free_memory trigger config stream module_read module_write
|
||||
websocket schema)
|
||||
websocket)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
AuthQuery() = default;
|
||||
@@ -2296,7 +2295,7 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
|
||||
AuthQuery::Privilege::CONFIG, AuthQuery::Privilege::STREAM,
|
||||
AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
|
||||
AuthQuery::Privilege::WEBSOCKET, AuthQuery::Privilege::SCHEMA};
|
||||
AuthQuery::Privilege::WEBSOCKET};
|
||||
cpp<#
|
||||
|
||||
(lcp:define-class info-query (query)
|
||||
@@ -2666,37 +2665,5 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class schema-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(label "LabelIx" :scope :public
|
||||
:slk-load (lambda (member)
|
||||
#>cpp
|
||||
slk::Load(&self->${member}, reader, storage);
|
||||
cpp<#)
|
||||
:clone (lambda (source dest)
|
||||
#>cpp
|
||||
${dest} = storage->GetLabelIx(${source}.name);
|
||||
cpp<#))
|
||||
(schema_type_map "std::unordered_map<PropertyIx, common::SchemaType>"
|
||||
:slk-save #'slk-save-property-map
|
||||
:slk-load #'slk-load-property-map
|
||||
:scope :public))
|
||||
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(create-schema drop-schema show-schema show-schemas)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
SchemaQuery() = default;
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
(lcp:pop-namespace) ;; namespace memgraph
|
||||
|
||||
@@ -94,7 +94,6 @@ class StreamQuery;
|
||||
class SettingQuery;
|
||||
class VersionQuery;
|
||||
class Foreach;
|
||||
class SchemaQuery;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@@ -126,9 +125,9 @@ class ExpressionVisitor
|
||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
||||
StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
|
||||
class QueryVisitor
|
||||
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
|
||||
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
|
||||
IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery> {};
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/ast/ast_visitor.hpp"
|
||||
@@ -1339,7 +1338,6 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
|
||||
if (ctx->MODULE_READ()) return AuthQuery::Privilege::MODULE_READ;
|
||||
if (ctx->MODULE_WRITE()) return AuthQuery::Privilege::MODULE_WRITE;
|
||||
if (ctx->WEBSOCKET()) return AuthQuery::Privilege::WEBSOCKET;
|
||||
if (ctx->SCHEMA()) return AuthQuery::Privilege::SCHEMA;
|
||||
LOG_FATAL("Should not get here - unknown privilege!");
|
||||
}
|
||||
|
||||
@@ -2338,94 +2336,6 @@ antlrcpp::Any CypherMainVisitor::visitForeach(MemgraphCypher::ForeachContext *ct
|
||||
return for_each;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "SchemaQuery should have exactly one child!");
|
||||
auto *schema_query = ctx->children[0]->accept(this).as<SchemaQuery *>();
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMA;
|
||||
schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSchemas(MemgraphCypher::ShowSchemasContext * /*ctx*/) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMAS;
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) {
|
||||
MG_ASSERT(ctx->symbolicName());
|
||||
const auto property_type = utils::ToLowerCase(ctx->symbolicName()->accept(this).as<std::string>());
|
||||
if (property_type == "bool") {
|
||||
return common::SchemaType::BOOL;
|
||||
}
|
||||
if (property_type == "string") {
|
||||
return common::SchemaType::STRING;
|
||||
}
|
||||
if (property_type == "integer") {
|
||||
return common::SchemaType::INT;
|
||||
}
|
||||
if (property_type == "date") {
|
||||
return common::SchemaType::DATE;
|
||||
}
|
||||
if (property_type == "duration") {
|
||||
return common::SchemaType::DURATION;
|
||||
}
|
||||
if (property_type == "localdatetime") {
|
||||
return common::SchemaType::LOCALDATETIME;
|
||||
}
|
||||
if (property_type == "localtime") {
|
||||
return common::SchemaType::LOCALTIME;
|
||||
}
|
||||
throw SyntaxException("Property type must be one of the supported types!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitSchemaTypeMap(MemgraphCypher::SchemaTypeMapContext *ctx) {
|
||||
std::unordered_map<PropertyIx, common::SchemaType> map;
|
||||
for (auto *property_key_pair : ctx->propertyKeyTypePair()) {
|
||||
PropertyIx key = property_key_pair->propertyKeyName()->accept(this);
|
||||
common::SchemaType type = property_key_pair->propertyType()->accept(this);
|
||||
if (!map.insert({key, type}).second) {
|
||||
throw SemanticException("Same property name can't appear twice in a schema map.");
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::CREATE_SCHEMA;
|
||||
schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
|
||||
if (!ctx->schemaTypeMap()) {
|
||||
throw SemanticException("Schema property map must exist!");
|
||||
}
|
||||
schema_query->schema_type_map_ =
|
||||
ctx->schemaTypeMap()->accept(this).as<std::unordered_map<PropertyIx, common::SchemaType>>();
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::DROP_SCHEMA;
|
||||
schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
LabelIx CypherMainVisitor::AddLabel(const std::string &name) { return storage_->GetLabelIx(name); }
|
||||
|
||||
PropertyIx CypherMainVisitor::AddProperty(const std::string &name) { return storage_->GetPropertyIx(name); }
|
||||
|
||||
@@ -849,41 +849,6 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitForeach(MemgraphCypher::ForeachContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaTypeMap(MemgraphCypher::SchemaTypeMapContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchemas(MemgraphCypher::ShowSchemasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) override;
|
||||
|
||||
public:
|
||||
Query *query() { return query_; }
|
||||
const static std::string kAnonPrefix;
|
||||
|
||||
@@ -46,10 +46,10 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| DROP
|
||||
| DUMP
|
||||
| EXECUTE
|
||||
| FREE
|
||||
| FROM
|
||||
| FOR
|
||||
| FOREACH
|
||||
| FREE
|
||||
| FROM
|
||||
| GLOBAL
|
||||
| GRANT
|
||||
| HEADER
|
||||
@@ -76,8 +76,6 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| ROLE
|
||||
| ROLES
|
||||
| QUOTE
|
||||
| SCHEMA
|
||||
| SCHEMAS
|
||||
| SESSION
|
||||
| SETTING
|
||||
| SETTINGS
|
||||
@@ -124,7 +122,6 @@ query : cypherQuery
|
||||
| streamQuery
|
||||
| settingQuery
|
||||
| versionQuery
|
||||
| schemaQuery
|
||||
;
|
||||
|
||||
authQuery : createRole
|
||||
@@ -195,12 +192,6 @@ settingQuery : setSetting
|
||||
| showSettings
|
||||
;
|
||||
|
||||
schemaQuery : showSchema
|
||||
| showSchemas
|
||||
| createSchema
|
||||
| dropSchema
|
||||
;
|
||||
|
||||
loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
( IGNORE BAD ) ?
|
||||
( DELIMITER delimiter ) ?
|
||||
@@ -263,7 +254,6 @@ privilege : CREATE
|
||||
| MODULE_READ
|
||||
| MODULE_WRITE
|
||||
| WEBSOCKET
|
||||
| SCHEMA
|
||||
;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
@@ -384,17 +374,3 @@ showSetting : SHOW DATABASE SETTING settingName ;
|
||||
showSettings : SHOW DATABASE SETTINGS ;
|
||||
|
||||
versionQuery : SHOW VERSION ;
|
||||
|
||||
showSchema : SHOW SCHEMA ON ':' labelName ;
|
||||
|
||||
showSchemas : SHOW SCHEMAS ;
|
||||
|
||||
propertyType : symbolicName ;
|
||||
|
||||
propertyKeyTypePair : propertyKeyName propertyType ;
|
||||
|
||||
schemaTypeMap : '(' propertyKeyTypePair ( ',' propertyKeyTypePair )* ')' ;
|
||||
|
||||
createSchema : CREATE SCHEMA ON ':' labelName schemaTypeMap ;
|
||||
|
||||
dropSchema : DROP SCHEMA ON ':' labelName ;
|
||||
|
||||
@@ -89,8 +89,6 @@ REVOKE : R E V O K E ;
|
||||
ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SCHEMA : S C H E M A ;
|
||||
SCHEMAS : S C H E M A S ;
|
||||
SERVICE_URL : S E R V I C E UNDERSCORE U R L ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
|
||||
@@ -80,8 +80,6 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
|
||||
void Visit(VersionQuery & /*version_query*/) override { AddPrivilege(AuthQuery::Privilege::STATS); }
|
||||
|
||||
void Visit(SchemaQuery & /*schema_query*/) override { AddPrivilege(AuthQuery::Privilege::SCHEMA); }
|
||||
|
||||
bool PreVisit(Create & /*unused*/) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
|
||||
@@ -204,9 +204,8 @@ const trie::Trie kKeywords = {"union",
|
||||
"pulsar",
|
||||
"service_url",
|
||||
"version",
|
||||
"websocket",
|
||||
"foreach",
|
||||
"schema"};
|
||||
"websocket"
|
||||
"foreach"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/csv_parsing.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
@@ -821,108 +820,6 @@ Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶m
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interpreter_context,
|
||||
std::vector<Notification> *notifications) {
|
||||
Callback callback;
|
||||
switch (schema_query->action_) {
|
||||
case SchemaQuery::Action::SHOW_SCHEMAS: {
|
||||
callback.header = {"label", "primary_key", "primary_key_type"};
|
||||
callback.fn = [interpreter_context]() {
|
||||
auto *db = interpreter_context->db;
|
||||
auto schemas_info = db->ListAllSchemas();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(schemas_info.schemas.size());
|
||||
|
||||
for (const auto &[label_id, schema_types] : schemas_info.schemas) {
|
||||
std::vector<TypedValue> schema_info_row;
|
||||
schema_info_row.reserve(3);
|
||||
|
||||
schema_info_row.emplace_back(db->LabelToName(label_id));
|
||||
std::vector<std::string> primary_key_properties;
|
||||
primary_key_properties.reserve(schema_types.size());
|
||||
std::transform(schema_types.begin(), schema_types.end(), std::back_inserter(primary_key_properties),
|
||||
[&db](const auto &schema_type) {
|
||||
return db->PropertyToName(schema_type.property_id) +
|
||||
"::" + storage::SchemaTypeToString(schema_type.type);
|
||||
});
|
||||
|
||||
schema_info_row.emplace_back(utils::Join(primary_key_properties, ", "));
|
||||
schema_info_row.emplace_back(schema_types.size() == 1 ? "Single" : "Composite");
|
||||
|
||||
results.push_back(std::move(schema_info_row));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::SHOW_SCHEMA: {
|
||||
callback.header = {"property_name", "property_type"};
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
const auto schemas_info = db->GetSchema(label);
|
||||
MG_ASSERT(schemas_info.schemas.size() < 2, "There can be only one schema under single label!");
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
if (!schemas_info.schemas.empty()) {
|
||||
const auto schema = schemas_info.schemas[0];
|
||||
|
||||
for (const auto &schema_property : schema.second) {
|
||||
std::vector<TypedValue> schema_info_row;
|
||||
schema_info_row.reserve(2);
|
||||
|
||||
schema_info_row.emplace_back(db->PropertyToName(schema_property.property_id));
|
||||
schema_info_row.emplace_back(storage::SchemaTypeToString(schema_property.type));
|
||||
|
||||
results.push_back(std::move(schema_info_row));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::CREATE_SCHEMA: {
|
||||
auto schema_type_map = schema_query->schema_type_map_;
|
||||
if (schema_query->schema_type_map_.empty()) {
|
||||
throw SyntaxException("One or more types have to be defined in schema definition.");
|
||||
}
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_,
|
||||
schema_type_map = std::move(schema_type_map)]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
std::vector<storage::SchemaPropertyType> schemas_types;
|
||||
schemas_types.reserve(schema_type_map.size());
|
||||
for (const auto &schema_type : schema_type_map) {
|
||||
auto property_id = db->NameToProperty(schema_type.first.name);
|
||||
schemas_types.push_back({schema_type.second, property_id});
|
||||
}
|
||||
if (!db->CreateSchema(label, schemas_types)) {
|
||||
throw QueryException(fmt::format("Schema on label :{} already exists!", primary_label.name));
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CREATE_SCHEMA,
|
||||
fmt::format("Create schema on label :{}", schema_query->label_.name));
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::DROP_SCHEMA: {
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
|
||||
if (!db->DropSchema(label)) {
|
||||
throw QueryException(fmt::format("Schema on label :{} does not exist!", primary_label.name));
|
||||
}
|
||||
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_SCHEMA,
|
||||
fmt::format("Dropped schema on label :{}", schema_query->label_.name));
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
// Struct for lazy pulling from a vector
|
||||
struct PullPlanVector {
|
||||
explicit PullPlanVector(std::vector<std::vector<TypedValue>> values) : values_(std::move(values)) {}
|
||||
@@ -2118,32 +2015,6 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSchemaQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, std::vector<Notification> *notifications) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ConstraintInMulticommandTxException();
|
||||
}
|
||||
auto *schema_query = utils::Downcast<SchemaQuery>(parsed_query.query);
|
||||
MG_ASSERT(schema_query);
|
||||
auto callback = HandleSchemaQuery(schema_query, interpreter_context, notifications);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(callback.fn), action = QueryHandlerResult::NOTHING,
|
||||
pull_plan = std::shared_ptr<PullPlanVector>(nullptr)](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
|
||||
if (!pull_plan) {
|
||||
auto results = handler();
|
||||
pull_plan = std::make_shared<PullPlanVector>(std::move(results));
|
||||
}
|
||||
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
return action;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
void Interpreter::BeginTransaction() {
|
||||
const auto prepared_query = PrepareTransactionQuery("BEGIN");
|
||||
prepared_query.query_handler(nullptr, {});
|
||||
@@ -2277,9 +2148,6 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, &*execution_db_accessor_);
|
||||
} else if (utils::Downcast<VersionQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareVersionQuery(std::move(parsed_query), in_explicit_transaction_);
|
||||
} else if (utils::Downcast<SchemaQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareSchemaQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&query_execution->notifications);
|
||||
} else {
|
||||
LOG_FATAL("Should not get here -- unknown query type!");
|
||||
}
|
||||
|
||||
@@ -38,8 +38,6 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "CreateIndex"sv;
|
||||
case NotificationCode::CREATE_STREAM:
|
||||
return "CreateStream"sv;
|
||||
case NotificationCode::CREATE_SCHEMA:
|
||||
return "CreateSchema"sv;
|
||||
case NotificationCode::CHECK_STREAM:
|
||||
return "CheckStream"sv;
|
||||
case NotificationCode::CREATE_TRIGGER:
|
||||
@@ -50,8 +48,6 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "DropReplica"sv;
|
||||
case NotificationCode::DROP_INDEX:
|
||||
return "DropIndex"sv;
|
||||
case NotificationCode::DROP_SCHEMA:
|
||||
return "DropSchema"sv;
|
||||
case NotificationCode::DROP_STREAM:
|
||||
return "DropStream"sv;
|
||||
case NotificationCode::DROP_TRIGGER:
|
||||
@@ -72,10 +68,6 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "ReplicaPortWarning"sv;
|
||||
case NotificationCode::SET_REPLICA:
|
||||
return "SetReplica"sv;
|
||||
case NotificationCode::SHOW_SCHEMA:
|
||||
return "ShowSchema"sv;
|
||||
case NotificationCode::SHOW_SCHEMAS:
|
||||
return "ShowSchemas"sv;
|
||||
case NotificationCode::START_STREAM:
|
||||
return "StartStream"sv;
|
||||
case NotificationCode::START_ALL_STREAMS:
|
||||
@@ -122,4 +114,4 @@ std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::query
|
||||
} // namespace memgraph::query
|
||||
@@ -26,14 +26,12 @@ enum class SeverityLevel : uint8_t { INFO, WARNING };
|
||||
enum class NotificationCode : uint8_t {
|
||||
CREATE_CONSTRAINT,
|
||||
CREATE_INDEX,
|
||||
CREATE_SCHEMA,
|
||||
CHECK_STREAM,
|
||||
CREATE_STREAM,
|
||||
CREATE_TRIGGER,
|
||||
DROP_CONSTRAINT,
|
||||
DROP_INDEX,
|
||||
DROP_REPLICA,
|
||||
DROP_SCHEMA,
|
||||
DROP_STREAM,
|
||||
DROP_TRIGGER,
|
||||
EXISTANT_INDEX,
|
||||
@@ -44,8 +42,6 @@ enum class NotificationCode : uint8_t {
|
||||
REPLICA_PORT_WARNING,
|
||||
REGISTER_REPLICA,
|
||||
SET_REPLICA,
|
||||
SHOW_SCHEMA,
|
||||
SHOW_SCHEMAS,
|
||||
START_STREAM,
|
||||
START_ALL_STREAMS,
|
||||
STOP_STREAM,
|
||||
|
||||
@@ -24,7 +24,7 @@ MgpUniquePtr<mgp_value> GetStringValueOrSetError(const char *string, mgp_memory
|
||||
}
|
||||
|
||||
bool InsertResultOrSetError(mgp_result *result, mgp_result_record *record, const char *result_name, mgp_value *value) {
|
||||
if (const auto err = mgp_result_record_insert(record, result_name, value); err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (const auto err = mgp_result_record_insert(record, result_name, value); err != MGP_ERROR_NO_ERROR) {
|
||||
const auto error_msg = fmt::format("Unable to set the result for {}, error = {}", result_name, err);
|
||||
static_cast<void>(mgp_result_set_error_msg(result, error_msg.c_str()));
|
||||
return false;
|
||||
|
||||
@@ -25,7 +25,7 @@ TResult Call(TFunc func, TArgs... args) {
|
||||
static_assert(std::is_trivially_copyable_v<TFunc>);
|
||||
static_assert((std::is_trivially_copyable_v<std::remove_reference_t<TArgs>> && ...));
|
||||
TResult result{};
|
||||
MG_ASSERT(func(args..., &result) == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(func(args..., &result) == MGP_ERROR_NO_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -50,10 +50,10 @@ mgp_error CreateMgpObject(MgpUniquePtr<TObj> &obj, TFunc func, TArgs &&...args)
|
||||
|
||||
template <typename Fun>
|
||||
[[nodiscard]] bool TryOrSetError(Fun &&func, mgp_result *result) {
|
||||
if (const auto err = func(); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = func(); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return false;
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
const auto error_msg = fmt::format("Unexpected error ({})!", err);
|
||||
static_cast<void>(mgp_result_set_error_msg(result, error_msg.c_str()));
|
||||
return false;
|
||||
|
||||
@@ -143,48 +143,48 @@ template <typename TFunc, typename... Args>
|
||||
WrapExceptionsHelper(std::forward<TFunc>(func), std::forward<Args>(args)...);
|
||||
} catch (const DeletedObjectException &neoe) {
|
||||
spdlog::error("Deleted object error during mg API call: {}", neoe.what());
|
||||
return mgp_error::MGP_ERROR_DELETED_OBJECT;
|
||||
return MGP_ERROR_DELETED_OBJECT;
|
||||
} catch (const KeyAlreadyExistsException &kaee) {
|
||||
spdlog::error("Key already exists error during mg API call: {}", kaee.what());
|
||||
return mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS;
|
||||
return MGP_ERROR_KEY_ALREADY_EXISTS;
|
||||
} catch (const InsufficientBufferException &ibe) {
|
||||
spdlog::error("Insufficient buffer error during mg API call: {}", ibe.what());
|
||||
return mgp_error::MGP_ERROR_INSUFFICIENT_BUFFER;
|
||||
return MGP_ERROR_INSUFFICIENT_BUFFER;
|
||||
} catch (const ImmutableObjectException &ioe) {
|
||||
spdlog::error("Immutable object error during mg API call: {}", ioe.what());
|
||||
return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT;
|
||||
return MGP_ERROR_IMMUTABLE_OBJECT;
|
||||
} catch (const ValueConversionException &vce) {
|
||||
spdlog::error("Value converion error during mg API call: {}", vce.what());
|
||||
return mgp_error::MGP_ERROR_VALUE_CONVERSION;
|
||||
return MGP_ERROR_VALUE_CONVERSION;
|
||||
} catch (const SerializationException &se) {
|
||||
spdlog::error("Serialization error during mg API call: {}", se.what());
|
||||
return mgp_error::MGP_ERROR_SERIALIZATION_ERROR;
|
||||
return MGP_ERROR_SERIALIZATION_ERROR;
|
||||
} catch (const std::bad_alloc &bae) {
|
||||
spdlog::error("Memory allocation error during mg API call: {}", bae.what());
|
||||
return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE;
|
||||
return MGP_ERROR_UNABLE_TO_ALLOCATE;
|
||||
} catch (const memgraph::utils::OutOfMemoryException &oome) {
|
||||
spdlog::error("Memory limit exceeded during mg API call: {}", oome.what());
|
||||
return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE;
|
||||
return MGP_ERROR_UNABLE_TO_ALLOCATE;
|
||||
} catch (const std::out_of_range &oore) {
|
||||
spdlog::error("Out of range error during mg API call: {}", oore.what());
|
||||
return mgp_error::MGP_ERROR_OUT_OF_RANGE;
|
||||
return MGP_ERROR_OUT_OF_RANGE;
|
||||
} catch (const std::invalid_argument &iae) {
|
||||
spdlog::error("Invalid argument error during mg API call: {}", iae.what());
|
||||
return mgp_error::MGP_ERROR_INVALID_ARGUMENT;
|
||||
return MGP_ERROR_INVALID_ARGUMENT;
|
||||
} catch (const std::logic_error &lee) {
|
||||
spdlog::error("Logic error during mg API call: {}", lee.what());
|
||||
return mgp_error::MGP_ERROR_LOGIC_ERROR;
|
||||
return MGP_ERROR_LOGIC_ERROR;
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Unexpected error during mg API call: {}", e.what());
|
||||
return mgp_error::MGP_ERROR_UNKNOWN_ERROR;
|
||||
return MGP_ERROR_UNKNOWN_ERROR;
|
||||
} catch (const memgraph::utils::temporal::InvalidArgumentException &e) {
|
||||
spdlog::error("Invalid argument was sent to an mg API call for temporal types: {}", e.what());
|
||||
return mgp_error::MGP_ERROR_INVALID_ARGUMENT;
|
||||
return MGP_ERROR_INVALID_ARGUMENT;
|
||||
} catch (...) {
|
||||
spdlog::error("Unexpected error during mg API call");
|
||||
return mgp_error::MGP_ERROR_UNKNOWN_ERROR;
|
||||
return MGP_ERROR_UNKNOWN_ERROR;
|
||||
}
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
// Graph mutations
|
||||
@@ -846,7 +846,7 @@ mgp_value_type MgpValueGetType(const mgp_value &val) noexcept { return val.type;
|
||||
mgp_error mgp_value_get_type(mgp_value *val, mgp_value_type *result) {
|
||||
static_assert(noexcept(MgpValueGetType(*val)));
|
||||
*result = MgpValueGetType(*val);
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
@@ -854,7 +854,7 @@ mgp_error mgp_value_get_type(mgp_value *val, mgp_value_type *result) {
|
||||
mgp_error mgp_value_is_##type_lowercase(mgp_value *val, int *result) { \
|
||||
static_assert(noexcept(MgpValueGetType(*val))); \
|
||||
*result = MgpValueGetType(*val) == MGP_VALUE_TYPE_##type_uppercase; \
|
||||
return mgp_error::MGP_ERROR_NO_ERROR; \
|
||||
return MGP_ERROR_NO_ERROR; \
|
||||
}
|
||||
|
||||
DEFINE_MGP_VALUE_IS(null, NULL)
|
||||
@@ -874,27 +874,27 @@ DEFINE_MGP_VALUE_IS(duration, DURATION)
|
||||
|
||||
mgp_error mgp_value_get_bool(mgp_value *val, int *result) {
|
||||
*result = val->bool_v ? 1 : 0;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
mgp_error mgp_value_get_int(mgp_value *val, int64_t *result) {
|
||||
*result = val->int_v;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
mgp_error mgp_value_get_double(mgp_value *val, double *result) {
|
||||
*result = val->double_v;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
mgp_error mgp_value_get_string(mgp_value *val, const char **result) {
|
||||
static_assert(noexcept(val->string_v.c_str()));
|
||||
*result = val->string_v.c_str();
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define DEFINE_MGP_VALUE_GET(type) \
|
||||
mgp_error mgp_value_get_##type(mgp_value *val, mgp_##type **result) { \
|
||||
*result = val->type##_v; \
|
||||
return mgp_error::MGP_ERROR_NO_ERROR; \
|
||||
return MGP_ERROR_NO_ERROR; \
|
||||
}
|
||||
|
||||
DEFINE_MGP_VALUE_GET(list)
|
||||
@@ -940,13 +940,13 @@ mgp_error mgp_list_append_extend(mgp_list *list, mgp_value *val) {
|
||||
mgp_error mgp_list_size(mgp_list *list, size_t *result) {
|
||||
static_assert(noexcept(list->elems.size()));
|
||||
*result = list->elems.size();
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_list_capacity(mgp_list *list, size_t *result) {
|
||||
static_assert(noexcept(list->elems.capacity()));
|
||||
*result = list->elems.capacity();
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_list_at(mgp_list *list, size_t i, mgp_value **result) {
|
||||
@@ -978,7 +978,7 @@ mgp_error mgp_map_insert(mgp_map *map, const char *key, mgp_value *value) {
|
||||
mgp_error mgp_map_size(mgp_map *map, size_t *result) {
|
||||
static_assert(noexcept(map->items.size()));
|
||||
*result = map->items.size();
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_map_at(mgp_map *map, const char *key, mgp_value **result) {
|
||||
@@ -1089,7 +1089,7 @@ size_t MgpPathSize(const mgp_path &path) noexcept { return path.edges.size(); }
|
||||
|
||||
mgp_error mgp_path_size(mgp_path *path, size_t *result) {
|
||||
*result = MgpPathSize(*path);
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_path_vertex_at(mgp_path *path, size_t i, mgp_vertex **result) {
|
||||
@@ -1690,7 +1690,7 @@ mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
|
||||
// NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
|
||||
static_assert(noexcept(*result = *v1 == *v2 ? 1 : 0));
|
||||
*result = *v1 == *v2 ? 1 : 0;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
|
||||
@@ -1950,7 +1950,7 @@ mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
|
||||
// NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
|
||||
static_assert(noexcept(*result = *e1 == *e2 ? 1 : 0));
|
||||
*result = *e1 == *e2 ? 1 : 0;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
|
||||
@@ -1967,12 +1967,12 @@ mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
|
||||
|
||||
mgp_error mgp_edge_get_from(mgp_edge *e, mgp_vertex **result) {
|
||||
*result = &e->from;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) {
|
||||
*result = &e->to;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
|
||||
@@ -2082,7 +2082,7 @@ mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_mem
|
||||
|
||||
mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
|
||||
*result = MgpGraphIsMutable(*graph) ? 1 : 0;
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
};
|
||||
|
||||
mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) {
|
||||
@@ -2507,7 +2507,7 @@ mgp_error mgp_proc_add_result(mgp_proc *proc, const char *name, mgp_type *type)
|
||||
|
||||
mgp_error MgpTransAddFixedResult(mgp_trans *trans) noexcept {
|
||||
if (const auto err = AddResultToProp(trans, "query", Call<mgp_type *>(mgp_type_string), false);
|
||||
err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
err != MGP_ERROR_NO_ERROR) {
|
||||
return err;
|
||||
}
|
||||
return AddResultToProp(trans, "parameters", Call<mgp_type *>(mgp_type_nullable, Call<mgp_type *>(mgp_type_map)),
|
||||
@@ -2754,7 +2754,7 @@ mgp_error mgp_message_offset(struct mgp_message *message, int64_t *result) {
|
||||
mgp_error mgp_messages_size(mgp_messages *messages, size_t *result) {
|
||||
static_assert(noexcept(messages->messages.size()));
|
||||
*result = messages->messages.size();
|
||||
return mgp_error::MGP_ERROR_NO_ERROR;
|
||||
return MGP_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
mgp_error mgp_messages_at(mgp_messages *messages, size_t index, mgp_message **result) {
|
||||
|
||||
@@ -121,18 +121,18 @@ void RegisterMgLoad(ModuleRegistry *module_registry, utils::RWLock *lock, Builti
|
||||
bool succ = false;
|
||||
WithUpgradedLock(lock, [&]() {
|
||||
const char *arg_as_string{nullptr};
|
||||
if (const auto err = mgp_value_get_string(arg, &arg_as_string); err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (const auto err = mgp_value_get_string(arg, &arg_as_string); err != MGP_ERROR_NO_ERROR) {
|
||||
succ = false;
|
||||
} else {
|
||||
succ = module_registry->LoadOrReloadModuleFromName(arg_as_string);
|
||||
}
|
||||
});
|
||||
if (!succ) {
|
||||
MG_ASSERT(mgp_result_set_error_msg(result, "Failed to (re)load the module.") == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_result_set_error_msg(result, "Failed to (re)load the module.") == MGP_ERROR_NO_ERROR);
|
||||
}
|
||||
};
|
||||
mgp_proc load("load", load_cb, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_arg(&load, "module_name", Call<mgp_type *>(mgp_type_string)) == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&load, "module_name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("load", std::move(load));
|
||||
}
|
||||
|
||||
@@ -235,16 +235,11 @@ void RegisterMgProcedures(
|
||||
}
|
||||
};
|
||||
mgp_proc procedures("procedures", procedures_cb, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_write", Call<mgp_type *>(mgp_type_bool)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_write", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("procedures", std::move(procedures));
|
||||
}
|
||||
|
||||
@@ -303,12 +298,9 @@ void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Modul
|
||||
}
|
||||
};
|
||||
mgp_proc procedures("transformations", transformations_cb, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("transformations", std::move(procedures));
|
||||
}
|
||||
|
||||
@@ -382,14 +374,10 @@ void RegisterMgFunctions(
|
||||
}
|
||||
};
|
||||
mgp_proc functions("functions", functions_cb, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "name", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "signature", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "is_editable", Call<mgp_type *>(mgp_type_bool)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "signature", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&functions, "is_editable", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("functions", std::move(functions));
|
||||
}
|
||||
namespace {
|
||||
@@ -481,10 +469,9 @@ void RegisterMgGetModuleFiles(ModuleRegistry *module_registry, BuiltinModule *mo
|
||||
|
||||
mgp_proc get_module_files("get_module_files", get_module_files_cb, utils::NewDeleteResource(),
|
||||
{.required_privilege = AuthQuery::Privilege::MODULE_READ});
|
||||
MG_ASSERT(mgp_proc_add_result(&get_module_files, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&get_module_files, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&get_module_files, "is_editable", Call<mgp_type *>(mgp_type_bool)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("get_module_files", std::move(get_module_files));
|
||||
}
|
||||
|
||||
@@ -543,10 +530,8 @@ void RegisterMgGetModuleFile(ModuleRegistry *module_registry, BuiltinModule *mod
|
||||
};
|
||||
mgp_proc get_module_file("get_module_file", std::move(get_module_file_cb), utils::NewDeleteResource(),
|
||||
{.required_privilege = AuthQuery::Privilege::MODULE_READ});
|
||||
MG_ASSERT(mgp_proc_add_arg(&get_module_file, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&get_module_file, "content", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&get_module_file, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&get_module_file, "content", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("get_module_file", std::move(get_module_file));
|
||||
}
|
||||
|
||||
@@ -624,12 +609,9 @@ void RegisterMgCreateModuleFile(ModuleRegistry *module_registry, utils::RWLock *
|
||||
};
|
||||
mgp_proc create_module_file("create_module_file", std::move(create_module_file_cb), utils::NewDeleteResource(),
|
||||
{.required_privilege = AuthQuery::Privilege::MODULE_WRITE});
|
||||
MG_ASSERT(mgp_proc_add_arg(&create_module_file, "filename", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&create_module_file, "content", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&create_module_file, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&create_module_file, "filename", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&create_module_file, "content", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&create_module_file, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("create_module_file", std::move(create_module_file));
|
||||
}
|
||||
|
||||
@@ -682,10 +664,8 @@ void RegisterMgUpdateModuleFile(ModuleRegistry *module_registry, utils::RWLock *
|
||||
};
|
||||
mgp_proc update_module_file("update_module_file", std::move(update_module_file_cb), utils::NewDeleteResource(),
|
||||
{.required_privilege = AuthQuery::Privilege::MODULE_WRITE});
|
||||
MG_ASSERT(mgp_proc_add_arg(&update_module_file, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&update_module_file, "content", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&update_module_file, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&update_module_file, "content", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("update_module_file", std::move(update_module_file));
|
||||
}
|
||||
|
||||
@@ -741,8 +721,7 @@ void RegisterMgDeleteModuleFile(ModuleRegistry *module_registry, utils::RWLock *
|
||||
};
|
||||
mgp_proc delete_module_file("delete_module_file", std::move(delete_module_file_cb), utils::NewDeleteResource(),
|
||||
{.required_privilege = AuthQuery::Privilege::MODULE_WRITE});
|
||||
MG_ASSERT(mgp_proc_add_arg(&delete_module_file, "path", Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&delete_module_file, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("delete_module_file", std::move(delete_module_file));
|
||||
}
|
||||
|
||||
@@ -822,8 +801,7 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
|
||||
spdlog::info("Loading module {}...", file_path);
|
||||
file_path_ = file_path;
|
||||
dlerror(); // Clear any existing error.
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
|
||||
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle_) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, dlerror(), "https://memgr.ph/modules"));
|
||||
@@ -854,8 +832,8 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
|
||||
return with_error(error);
|
||||
}
|
||||
for (auto &trans : module_def->transformations) {
|
||||
const bool success = mgp_error::MGP_ERROR_NO_ERROR == MgpTransAddFixedResult(&trans.second);
|
||||
if (!success) {
|
||||
const bool was_result_added = MgpTransAddFixedResult(&trans.second);
|
||||
if (!was_result_added) {
|
||||
const auto error =
|
||||
fmt::format("Unable to add result to transformation in module {}; add result failed", file_path);
|
||||
return with_error(error);
|
||||
@@ -963,7 +941,7 @@ bool PythonModule::Load(const std::filesystem::path &file_path) {
|
||||
auto module_cb = [&](auto *module_def, auto * /*memory*/) {
|
||||
auto result = ImportPyModule(file_path.stem().c_str(), module_def);
|
||||
for (auto &trans : module_def->transformations) {
|
||||
succ = MgpTransAddFixedResult(&trans.second) == mgp_error::MGP_ERROR_NO_ERROR;
|
||||
succ = MgpTransAddFixedResult(&trans.second) == MGP_ERROR_NO_ERROR;
|
||||
if (!succ) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
/// API for loading and registering modules providing custom oC procedures
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
@@ -129,40 +128,6 @@ class ModuleRegistry final {
|
||||
const std::filesystem::path &InternalModuleDir() const noexcept;
|
||||
|
||||
private:
|
||||
class SharedLibraryHandle {
|
||||
public:
|
||||
SharedLibraryHandle(const std::string &shared_library, int mode) : handle_{dlopen(shared_library.c_str(), mode)} {}
|
||||
SharedLibraryHandle(const SharedLibraryHandle &) = delete;
|
||||
SharedLibraryHandle(SharedLibraryHandle &&) = delete;
|
||||
SharedLibraryHandle operator=(const SharedLibraryHandle &) = delete;
|
||||
SharedLibraryHandle operator=(SharedLibraryHandle &&) = delete;
|
||||
|
||||
~SharedLibraryHandle() {
|
||||
if (handle_) {
|
||||
dlclose(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void *handle_;
|
||||
};
|
||||
|
||||
#if __has_feature(address_sanitizer)
|
||||
// This is why we need RTLD_NODELETE and we must not use RTLD_DEEPBIND with
|
||||
// ASAN: https://github.com/google/sanitizers/issues/89
|
||||
SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE};
|
||||
#else
|
||||
// The reason behind opening share library during runtime is to avoid issues
|
||||
// with loading symbols from stdlib. We have encounter issues with locale
|
||||
// that cause std::cout not being printed and issues when python libraries
|
||||
// would call stdlib (e.g. pytorch).
|
||||
// The way that those issues were solved was
|
||||
// by using RTLD_DEEPBIND. RTLD_DEEPBIND ensures that the lookup for the
|
||||
// mentioned library will be first performed in the already existing binded
|
||||
// libraries and then the global namespace.
|
||||
// RTLD_DEEPBIND => https://linux.die.net/man/3/dlopen
|
||||
SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND};
|
||||
#endif
|
||||
std::vector<std::filesystem::path> modules_dirs_;
|
||||
std::filesystem::path internal_module_dir_;
|
||||
};
|
||||
|
||||
@@ -55,49 +55,49 @@ PyObject *gMgpSerializationError{nullptr}; // NOLINT(cppcoreguidelines-avo
|
||||
// Returns true if an exception is raised
|
||||
bool RaiseExceptionFromErrorCode(const mgp_error error) {
|
||||
switch (error) {
|
||||
case mgp_error::MGP_ERROR_NO_ERROR:
|
||||
case MGP_ERROR_NO_ERROR:
|
||||
return false;
|
||||
case mgp_error::MGP_ERROR_UNKNOWN_ERROR: {
|
||||
case MGP_ERROR_UNKNOWN_ERROR: {
|
||||
PyErr_SetString(gMgpUnknownError, "Unknown error happened.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE: {
|
||||
case MGP_ERROR_UNABLE_TO_ALLOCATE: {
|
||||
PyErr_SetString(gMgpUnableToAllocateError, "Unable to allocate memory.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_INSUFFICIENT_BUFFER: {
|
||||
case MGP_ERROR_INSUFFICIENT_BUFFER: {
|
||||
PyErr_SetString(gMgpInsufficientBufferError, "Insufficient buffer.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_OUT_OF_RANGE: {
|
||||
case MGP_ERROR_OUT_OF_RANGE: {
|
||||
PyErr_SetString(gMgpOutOfRangeError, "Out of range.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_LOGIC_ERROR: {
|
||||
case MGP_ERROR_LOGIC_ERROR: {
|
||||
PyErr_SetString(gMgpLogicErrorError, "Logic error.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_DELETED_OBJECT: {
|
||||
case MGP_ERROR_DELETED_OBJECT: {
|
||||
PyErr_SetString(gMgpDeletedObjectError, "Accessing deleted object.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_INVALID_ARGUMENT: {
|
||||
case MGP_ERROR_INVALID_ARGUMENT: {
|
||||
PyErr_SetString(gMgpInvalidArgumentError, "Invalid argument.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS: {
|
||||
case MGP_ERROR_KEY_ALREADY_EXISTS: {
|
||||
PyErr_SetString(gMgpKeyAlreadyExistsError, "Key already exists.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_IMMUTABLE_OBJECT: {
|
||||
case MGP_ERROR_IMMUTABLE_OBJECT: {
|
||||
PyErr_SetString(gMgpImmutableObjectError, "Cannot modify immutable object.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_VALUE_CONVERSION: {
|
||||
case MGP_ERROR_VALUE_CONVERSION: {
|
||||
PyErr_SetString(gMgpValueConversionError, "Value conversion failed.");
|
||||
return true;
|
||||
}
|
||||
case mgp_error::MGP_ERROR_SERIALIZATION_ERROR: {
|
||||
case MGP_ERROR_SERIALIZATION_ERROR: {
|
||||
PyErr_SetString(gMgpSerializationError, "Operation cannot be serialized.");
|
||||
return true;
|
||||
}
|
||||
@@ -902,7 +902,7 @@ std::optional<py::ExceptionInfo> AddRecordFromPython(mgp_result *result, py::Obj
|
||||
if (field_val == nullptr) {
|
||||
return py::FetchError();
|
||||
}
|
||||
if (mgp_result_record_insert(record, field_name, field_val) != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (mgp_result_record_insert(record, field_name, field_val) != MGP_ERROR_NO_ERROR) {
|
||||
std::stringstream ss;
|
||||
ss << "Unable to insert field '" << py::Object::FromBorrow(key) << "' with value: '"
|
||||
<< py::Object::FromBorrow(val) << "'; did you set the correct field type?";
|
||||
@@ -2281,10 +2281,9 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
auto py_seq_to_list = [memory](PyObject *seq, Py_ssize_t len, const auto &py_seq_get_item) {
|
||||
static_assert(std::numeric_limits<Py_ssize_t>::max() <= std::numeric_limits<size_t>::max());
|
||||
MgpUniquePtr<mgp_list> list{nullptr, &mgp_list_destroy};
|
||||
if (const auto err = CreateMgpObject(list, mgp_list_make_empty, len, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = CreateMgpObject(list, mgp_list_make_empty, len, memory); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during making mgp_list"};
|
||||
}
|
||||
for (Py_ssize_t i = 0; i < len; ++i) {
|
||||
@@ -2293,17 +2292,17 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
v = PyObjectToMgpValue(e, memory);
|
||||
const auto err = mgp_list_append(list.get(), v);
|
||||
mgp_value_destroy(v);
|
||||
if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (err != MGP_ERROR_NO_ERROR) {
|
||||
if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
throw std::runtime_error{"Unexpected error during appending to mgp_list"};
|
||||
}
|
||||
}
|
||||
mgp_value *v{nullptr};
|
||||
if (const auto err = mgp_value_make_list(list.get(), &v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_list(list.get(), &v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during making mgp_value"};
|
||||
}
|
||||
static_cast<void>(list.release());
|
||||
@@ -2335,7 +2334,7 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
};
|
||||
|
||||
mgp_value *mgp_v{nullptr};
|
||||
mgp_error last_error{mgp_error::MGP_ERROR_NO_ERROR};
|
||||
mgp_error last_error{MGP_ERROR_NO_ERROR};
|
||||
|
||||
if (o == Py_None) {
|
||||
last_error = mgp_value_make_null(memory, &mgp_v);
|
||||
@@ -2361,10 +2360,10 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
MgpUniquePtr<mgp_map> map{nullptr, mgp_map_destroy};
|
||||
const auto map_err = CreateMgpObject(map, mgp_map_make_empty, memory);
|
||||
|
||||
if (map_err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (map_err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
if (map_err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (map_err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during creating mgp_map"};
|
||||
}
|
||||
|
||||
@@ -2385,16 +2384,16 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
|
||||
MgpUniquePtr<mgp_value> v{PyObjectToMgpValue(value, memory), mgp_value_destroy};
|
||||
|
||||
if (const auto err = mgp_map_insert(map.get(), k, v.get()); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_map_insert(map.get(), k, v.get()); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during inserting an item to mgp_map"};
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto err = mgp_value_make_map(map.get(), &mgp_v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_map(map.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during creating mgp_value"};
|
||||
}
|
||||
static_cast<void>(map.release());
|
||||
@@ -2403,14 +2402,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
// Copy the edge and pass the ownership to the created mgp_value.
|
||||
|
||||
if (const auto err = CreateMgpObject(e, mgp_edge_copy, reinterpret_cast<PyEdge *>(o)->edge, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_edge"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_edge(e.get(), &mgp_v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_edge(e.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_edge"};
|
||||
}
|
||||
static_cast<void>(e.release());
|
||||
@@ -2419,14 +2418,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
// Copy the edge and pass the ownership to the created mgp_value.
|
||||
|
||||
if (const auto err = CreateMgpObject(p, mgp_path_copy, reinterpret_cast<PyPath *>(o)->path, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_path"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_path(p.get(), &mgp_v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_path(p.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_path"};
|
||||
}
|
||||
static_cast<void>(p.release());
|
||||
@@ -2435,14 +2434,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
// Copy the edge and pass the ownership to the created mgp_value.
|
||||
|
||||
if (const auto err = CreateMgpObject(v, mgp_vertex_copy, reinterpret_cast<PyVertex *>(o)->vertex, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_vertex"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_vertex(v.get(), &mgp_v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_vertex(v.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error during copying mgp_vertex"};
|
||||
}
|
||||
static_cast<void>(v.release());
|
||||
@@ -2475,14 +2474,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
MgpUniquePtr<mgp_date> date{nullptr, mgp_date_destroy};
|
||||
|
||||
if (const auto err = CreateMgpObject(date, mgp_date_from_parameters, ¶meters, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_date"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_date(date.get(), &mgp_v); err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_date(date.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_value"};
|
||||
}
|
||||
static_cast<void>(date.release());
|
||||
@@ -2500,15 +2499,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
MgpUniquePtr<mgp_local_time> local_time{nullptr, mgp_local_time_destroy};
|
||||
|
||||
if (const auto err = CreateMgpObject(local_time, mgp_local_time_from_parameters, ¶meters, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_local_time"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_local_time(local_time.get(), &mgp_v);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_local_time(local_time.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_value"};
|
||||
}
|
||||
static_cast<void>(local_time.release());
|
||||
@@ -2533,15 +2531,15 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
MgpUniquePtr<mgp_local_date_time> local_date_time{nullptr, mgp_local_date_time_destroy};
|
||||
|
||||
if (const auto err = CreateMgpObject(local_date_time, mgp_local_date_time_from_parameters, ¶meters, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_local_date_time"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_local_date_time(local_date_time.get(), &mgp_v);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_value"};
|
||||
}
|
||||
static_cast<void>(local_date_time.release());
|
||||
@@ -2560,15 +2558,14 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
MgpUniquePtr<mgp_duration> duration{nullptr, mgp_duration_destroy};
|
||||
|
||||
if (const auto err = CreateMgpObject(duration, mgp_duration_from_microseconds, microseconds, memory);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_duration"};
|
||||
}
|
||||
if (const auto err = mgp_value_make_duration(duration.get(), &mgp_v);
|
||||
err == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (const auto err = mgp_value_make_duration(duration.get(), &mgp_v); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
} else if (err != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_value"};
|
||||
}
|
||||
static_cast<void>(duration.release());
|
||||
@@ -2576,10 +2573,10 @@ mgp_value *PyObjectToMgpValue(PyObject *o, mgp_memory *memory) {
|
||||
throw std::invalid_argument("Unsupported PyObject conversion");
|
||||
}
|
||||
|
||||
if (last_error == mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
if (last_error == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
if (last_error != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (last_error != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error{"Unexpected error while creating mgp_value"};
|
||||
}
|
||||
|
||||
|
||||
@@ -181,27 +181,25 @@ void Streams::RegisterKafkaProcedures() {
|
||||
const auto offset = procedure::Call<int64_t>(mgp_value_get_int, arg_offset);
|
||||
auto lock_ptr = streams_.Lock();
|
||||
auto it = GetStream(*lock_ptr, std::string(stream_name));
|
||||
std::visit(utils::Overloaded{[&](StreamData<KafkaStream> &kafka_stream) {
|
||||
auto stream_source_ptr = kafka_stream.stream_source->Lock();
|
||||
const auto error = stream_source_ptr->SetStreamOffset(offset);
|
||||
if (error.HasError()) {
|
||||
MG_ASSERT(mgp_result_set_error_msg(result, error.GetError().c_str()) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR,
|
||||
"Unable to set procedure error message of procedure: {}", proc_name);
|
||||
}
|
||||
},
|
||||
[](auto && /*other*/) {
|
||||
throw QueryRuntimeException("'{}' can be only used for Kafka stream sources",
|
||||
proc_name);
|
||||
}},
|
||||
std::visit(utils::Overloaded{
|
||||
[&](StreamData<KafkaStream> &kafka_stream) {
|
||||
auto stream_source_ptr = kafka_stream.stream_source->Lock();
|
||||
const auto error = stream_source_ptr->SetStreamOffset(offset);
|
||||
if (error.HasError()) {
|
||||
MG_ASSERT(mgp_result_set_error_msg(result, error.GetError().c_str()) == MGP_ERROR_NO_ERROR,
|
||||
"Unable to set procedure error message of procedure: {}", proc_name);
|
||||
}
|
||||
},
|
||||
[](auto && /*other*/) {
|
||||
throw QueryRuntimeException("'{}' can be only used for Kafka stream sources", proc_name);
|
||||
}},
|
||||
it->second);
|
||||
};
|
||||
|
||||
mgp_proc proc(proc_name, set_stream_offset, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_arg(&proc, "stream_name", procedure::Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&proc, "offset", procedure::Call<mgp_type *>(mgp_type_int)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_arg(&proc, "offset", procedure::Call<mgp_type *>(mgp_type_int)) == MGP_ERROR_NO_ERROR);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
@@ -347,19 +345,19 @@ void Streams::RegisterKafkaProcedures() {
|
||||
|
||||
mgp_proc proc(proc_name, get_stream_info, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_arg(&proc, "stream_name", procedure::Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, consumer_group_result_name.data(),
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(
|
||||
mgp_proc_add_result(&proc, topics_result_name.data(),
|
||||
procedure::Call<mgp_type *>(mgp_type_list, procedure::Call<mgp_type *>(mgp_type_string))) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, bootstrap_servers_result_name.data(),
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, configs_result_name.data(), procedure::Call<mgp_type *>(mgp_type_map)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, credentials_result_name.data(), procedure::Call<mgp_type *>(mgp_type_map)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
@@ -434,14 +432,14 @@ void Streams::RegisterPulsarProcedures() {
|
||||
|
||||
mgp_proc proc(proc_name, get_stream_info, utils::NewDeleteResource());
|
||||
MG_ASSERT(mgp_proc_add_arg(&proc, "stream_name", procedure::Call<mgp_type *>(mgp_type_string)) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, service_url_result_name.data(),
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == mgp_error::MGP_ERROR_NO_ERROR);
|
||||
procedure::Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
|
||||
MG_ASSERT(
|
||||
mgp_proc_add_result(&proc, topics_result_name.data(),
|
||||
procedure::Call<mgp_type *>(mgp_type_list, procedure::Call<mgp_type *>(mgp_type_string))) ==
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ set(storage_v2_src_files
|
||||
indices.cpp
|
||||
property_store.cpp
|
||||
vertex_accessor.cpp
|
||||
schemas.cpp
|
||||
storage.cpp)
|
||||
|
||||
##### Replication #####
|
||||
|
||||
@@ -178,7 +178,6 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
|
||||
|
||||
RecoveryInfo recovery_info;
|
||||
RecoveredIndicesAndConstraints indices_constraints;
|
||||
memgraph::storage::SchemasMap recovered_schemas;
|
||||
std::optional<uint64_t> snapshot_timestamp;
|
||||
if (!snapshot_files.empty()) {
|
||||
spdlog::info("Try recovering from snapshot directory {}.", snapshot_directory);
|
||||
|
||||
@@ -38,7 +38,6 @@ enum class Marker : uint8_t {
|
||||
SECTION_DELTA = 0x26,
|
||||
SECTION_EPOCH_HISTORY = 0x27,
|
||||
SECTION_OFFSETS = 0x42,
|
||||
SECTION_SCHEMAS = 0x43,
|
||||
|
||||
DELTA_VERTEX_CREATE = 0x50,
|
||||
DELTA_VERTEX_DELETE = 0x51,
|
||||
@@ -57,8 +56,6 @@ enum class Marker : uint8_t {
|
||||
DELTA_EXISTENCE_CONSTRAINT_DROP = 0x5e,
|
||||
DELTA_UNIQUE_CONSTRAINT_CREATE = 0x5f,
|
||||
DELTA_UNIQUE_CONSTRAINT_DROP = 0x60,
|
||||
DELTA_SCHEMA_CREATE = 0x61,
|
||||
DELTA_SCHEMA_DROP = 0x62,
|
||||
|
||||
VALUE_FALSE = 0x00,
|
||||
VALUE_TRUE = 0xff,
|
||||
@@ -66,7 +63,7 @@ enum class Marker : uint8_t {
|
||||
|
||||
/// List of all available markers.
|
||||
/// IMPORTANT: Don't forget to update this list when you add a new Marker.
|
||||
constexpr Marker kMarkersAll[] = {
|
||||
static const Marker kMarkersAll[] = {
|
||||
Marker::TYPE_NULL,
|
||||
Marker::TYPE_BOOL,
|
||||
Marker::TYPE_INT,
|
||||
@@ -102,8 +99,6 @@ constexpr Marker kMarkersAll[] = {
|
||||
Marker::DELTA_EXISTENCE_CONSTRAINT_DROP,
|
||||
Marker::DELTA_UNIQUE_CONSTRAINT_CREATE,
|
||||
Marker::DELTA_UNIQUE_CONSTRAINT_DROP,
|
||||
Marker::DELTA_SCHEMA_CREATE,
|
||||
Marker::DELTA_SCHEMA_DROP,
|
||||
Marker::VALUE_FALSE,
|
||||
Marker::VALUE_TRUE,
|
||||
};
|
||||
|
||||
@@ -333,7 +333,6 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() {
|
||||
case Marker::SECTION_DELTA:
|
||||
case Marker::SECTION_EPOCH_HISTORY:
|
||||
case Marker::SECTION_OFFSETS:
|
||||
case Marker::SECTION_SCHEMAS:
|
||||
case Marker::DELTA_VERTEX_CREATE:
|
||||
case Marker::DELTA_VERTEX_DELETE:
|
||||
case Marker::DELTA_VERTEX_ADD_LABEL:
|
||||
@@ -351,8 +350,6 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() {
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_SCHEMA_CREATE:
|
||||
case Marker::DELTA_SCHEMA_DROP:
|
||||
case Marker::VALUE_FALSE:
|
||||
case Marker::VALUE_TRUE:
|
||||
return std::nullopt;
|
||||
@@ -435,7 +432,6 @@ bool Decoder::SkipPropertyValue() {
|
||||
case Marker::SECTION_DELTA:
|
||||
case Marker::SECTION_EPOCH_HISTORY:
|
||||
case Marker::SECTION_OFFSETS:
|
||||
case Marker::SECTION_SCHEMAS:
|
||||
case Marker::DELTA_VERTEX_CREATE:
|
||||
case Marker::DELTA_VERTEX_DELETE:
|
||||
case Marker::DELTA_VERTEX_ADD_LABEL:
|
||||
@@ -453,8 +449,6 @@ bool Decoder::SkipPropertyValue() {
|
||||
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_CREATE:
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
case Marker::DELTA_SCHEMA_CREATE:
|
||||
case Marker::DELTA_SCHEMA_DROP:
|
||||
case Marker::VALUE_FALSE:
|
||||
case Marker::VALUE_TRUE:
|
||||
return false;
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace memgraph::storage::durability {
|
||||
|
||||
/// Structure used to hold information about a snapshot.
|
||||
struct SnapshotInfo {
|
||||
uint64_t offset_schemas;
|
||||
uint64_t offset_edges;
|
||||
uint64_t offset_vertices;
|
||||
uint64_t offset_indices;
|
||||
|
||||
@@ -69,10 +69,6 @@ namespace memgraph::storage::durability {
|
||||
// * unique constraint create, unique constraint drop
|
||||
// * label name
|
||||
// * property names
|
||||
// * schema create, schema drop
|
||||
// * label name
|
||||
// * property names
|
||||
// * property type
|
||||
//
|
||||
// IMPORTANT: When changing WAL encoding/decoding bump the snapshot/WAL version
|
||||
// in `version.hpp`.
|
||||
@@ -97,10 +93,6 @@ Marker OperationToMarker(StorageGlobalOperation operation) {
|
||||
return Marker::DELTA_UNIQUE_CONSTRAINT_CREATE;
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP:
|
||||
return Marker::DELTA_UNIQUE_CONSTRAINT_DROP;
|
||||
case StorageGlobalOperation::SCHEMA_CREATE:
|
||||
return Marker::DELTA_SCHEMA_CREATE;
|
||||
case StorageGlobalOperation::SCHEMA_DROP:
|
||||
return Marker::DELTA_SCHEMA_DROP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +122,7 @@ Marker VertexActionToMarker(Delta::Action action) {
|
||||
}
|
||||
}
|
||||
|
||||
// This function converts a Marker to a WalDeltaData::Type. It checks for the
|
||||
// This function convertes a Marker to a WalDeltaData::Type. It checks for the
|
||||
// validity of the marker and throws if an invalid marker is specified.
|
||||
// @throw RecoveryFailure
|
||||
WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) {
|
||||
@@ -169,10 +161,6 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) {
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE;
|
||||
case Marker::DELTA_UNIQUE_CONSTRAINT_DROP:
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP;
|
||||
case Marker::DELTA_SCHEMA_CREATE:
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE;
|
||||
case Marker::DELTA_SCHEMA_DROP:
|
||||
return WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP;
|
||||
|
||||
case Marker::TYPE_NULL:
|
||||
case Marker::TYPE_BOOL:
|
||||
@@ -321,11 +309,6 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
|
||||
if (!decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::SCHEMA_CREATE:
|
||||
case WalDeltaData::Type::SCHEMA_DROP: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,10 +456,6 @@ bool operator==(const WalDeltaData &a, const WalDeltaData &b) {
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
|
||||
return a.operation_label_properties.label == b.operation_label_properties.label &&
|
||||
a.operation_label_properties.properties == b.operation_label_properties.properties;
|
||||
case WalDeltaData::Type::SCHEMA_CREATE:
|
||||
case WalDeltaData::Type::SCHEMA_DROP: {
|
||||
return a.operation_label_create_schema.label == b.operation_label_create_schema.label;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool operator!=(const WalDeltaData &a, const WalDeltaData &b) { return !(a == b); }
|
||||
@@ -636,49 +615,6 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage
|
||||
}
|
||||
break;
|
||||
}
|
||||
case StorageGlobalOperation::SCHEMA_CREATE:
|
||||
case StorageGlobalOperation::SCHEMA_DROP: {
|
||||
MG_ASSERT(!properties.empty(), "Invalid function call!");
|
||||
encoder->WriteMarker(OperationToMarker(operation));
|
||||
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
|
||||
encoder->WriteUint(properties.size());
|
||||
for (const auto &property : properties) {
|
||||
encoder->WriteString(name_id_mapper->IdToName(property.AsUint()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
|
||||
const Schemas::Schema &schema, uint64_t timestamp) {
|
||||
encoder->WriteMarker(Marker::SECTION_DELTA);
|
||||
encoder->WriteUint(timestamp);
|
||||
switch (operation) {
|
||||
case StorageGlobalOperation::LABEL_INDEX_CREATE:
|
||||
case StorageGlobalOperation::LABEL_INDEX_DROP:
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE:
|
||||
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP:
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE:
|
||||
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP:
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE:
|
||||
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP:
|
||||
case StorageGlobalOperation::SCHEMA_DROP: {
|
||||
throw RecoveryFailure("Unsupported action!");
|
||||
}
|
||||
case StorageGlobalOperation::SCHEMA_CREATE: {
|
||||
encoder->WriteMarker(OperationToMarker(operation));
|
||||
encoder->WriteString(name_id_mapper->IdToName(schema.first.AsUint()));
|
||||
encoder->WriteUint(schema.second.size());
|
||||
for (const auto &schema_type : schema.second) {
|
||||
encoder->WriteString(name_id_mapper->IdToName(schema_type.property_id.AsUint()));
|
||||
}
|
||||
encoder->WriteUint(schema.second.size());
|
||||
for (const auto &schema_type : schema.second) {
|
||||
encoder->WriteUint(static_cast<uint64_t>(schema_type.type));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,10 +847,6 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst
|
||||
"The unique constraint doesn't exist!");
|
||||
break;
|
||||
}
|
||||
case WalDeltaData::Type::SCHEMA_CREATE:
|
||||
case WalDeltaData::Type::SCHEMA_DROP: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret.next_timestamp = std::max(ret.next_timestamp, timestamp + 1);
|
||||
++deltas_applied;
|
||||
@@ -1035,11 +967,6 @@ void WalFile::AppendOperation(StorageGlobalOperation operation, LabelId label, c
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::AppendOperation(StorageGlobalOperation operation, const Schemas::Schema &schema, uint64_t timestamp) {
|
||||
EncodeOperation(&wal_, name_id_mapper_, operation, schema, timestamp);
|
||||
UpdateStats(timestamp);
|
||||
}
|
||||
|
||||
void WalFile::Sync() { wal_.Sync(); }
|
||||
|
||||
uint64_t WalFile::GetSize() { return wal_.GetSize(); }
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
@@ -64,8 +63,6 @@ struct WalDeltaData {
|
||||
EXISTENCE_CONSTRAINT_DROP,
|
||||
UNIQUE_CONSTRAINT_CREATE,
|
||||
UNIQUE_CONSTRAINT_DROP,
|
||||
SCHEMA_CREATE,
|
||||
SCHEMA_DROP,
|
||||
};
|
||||
|
||||
Type type{Type::TRANSACTION_END};
|
||||
@@ -105,11 +102,6 @@ struct WalDeltaData {
|
||||
std::string label;
|
||||
std::set<std::string> properties;
|
||||
} operation_label_properties;
|
||||
|
||||
struct {
|
||||
std::string label;
|
||||
std::vector<SchemaPropertyType> schema_properties_types;
|
||||
} operation_label_create_schema;
|
||||
};
|
||||
|
||||
bool operator==(const WalDeltaData &a, const WalDeltaData &b);
|
||||
@@ -125,8 +117,6 @@ enum class StorageGlobalOperation {
|
||||
EXISTENCE_CONSTRAINT_DROP,
|
||||
UNIQUE_CONSTRAINT_CREATE,
|
||||
UNIQUE_CONSTRAINT_DROP,
|
||||
SCHEMA_CREATE,
|
||||
SCHEMA_DROP,
|
||||
};
|
||||
|
||||
constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
|
||||
@@ -158,8 +148,6 @@ constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
|
||||
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
|
||||
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
|
||||
case WalDeltaData::Type::SCHEMA_CREATE:
|
||||
case WalDeltaData::Type::SCHEMA_DROP:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -200,10 +188,6 @@ void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp);
|
||||
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
|
||||
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp);
|
||||
|
||||
/// Function used to encode non-transactional operation related.
|
||||
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
|
||||
const Schemas::Schema &schema, uint64_t timestamp);
|
||||
|
||||
/// Function used to load the WAL data into the storage.
|
||||
/// @throw RecoveryFailure
|
||||
RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints,
|
||||
@@ -234,8 +218,6 @@ class WalFile {
|
||||
void AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t timestamp);
|
||||
|
||||
void AppendOperation(StorageGlobalOperation operation, const Schemas::Schema &schema, uint64_t timestamp);
|
||||
|
||||
void Sync();
|
||||
|
||||
uint64_t GetSize();
|
||||
|
||||
@@ -562,12 +562,6 @@ void Storage::ReplicationClient::ReplicaStream::AppendOperation(durability::Stor
|
||||
EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, label, properties, timestamp);
|
||||
}
|
||||
|
||||
void Storage::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation,
|
||||
const Schemas::Schema &schema, uint64_t timestamp) {
|
||||
replication::Encoder encoder(stream_.GetBuilder());
|
||||
EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, schema, timestamp);
|
||||
}
|
||||
|
||||
replication::AppendDeltasRes Storage::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); }
|
||||
|
||||
////// CurrentWalHandler //////
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "storage/v2/replication/serialization.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
@@ -63,10 +62,6 @@ class Storage::ReplicationClient {
|
||||
void AppendOperation(durability::StorageGlobalOperation operation, LabelId label,
|
||||
const std::set<PropertyId> &properties, uint64_t timestamp);
|
||||
|
||||
/// @throw rpc::RpcFailedException
|
||||
void AppendOperation(durability::StorageGlobalOperation operation, const Schemas::Schema &schema,
|
||||
uint64_t timestamp);
|
||||
|
||||
private:
|
||||
/// @throw rpc::RpcFailedException
|
||||
replication::AppendDeltasRes Finalize();
|
||||
|
||||
@@ -312,13 +312,13 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
switch (delta.type) {
|
||||
case durability::WalDeltaData::Type::VERTEX_CREATE: {
|
||||
spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
transaction->CreateVertex(delta.vertex_create_delete.gid);
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::VERTEX_DELETE: {
|
||||
spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, storage::View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = transaction->DeleteVertex(&*vertex);
|
||||
@@ -328,7 +328,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: {
|
||||
spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(),
|
||||
delta.vertex_add_remove_label.label);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, storage::View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||
@@ -338,7 +338,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
|
||||
spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(),
|
||||
delta.vertex_add_remove_label.label);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, storage::View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label));
|
||||
@@ -348,7 +348,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: {
|
||||
spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
|
||||
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, storage::View::NEW);
|
||||
if (!vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||
@@ -360,7 +360,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}",
|
||||
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
|
||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, storage::View::NEW);
|
||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, storage::View::NEW);
|
||||
@@ -375,7 +375,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}",
|
||||
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
|
||||
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, storage::View::NEW);
|
||||
if (!from_vertex) throw utils::BasicException("Invalid transaction!");
|
||||
auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, storage::View::NEW);
|
||||
@@ -398,7 +398,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
"Can't set properties on edges because properties on edges "
|
||||
"are disabled!");
|
||||
|
||||
auto *transaction = get_transaction(timestamp);
|
||||
auto transaction = get_transaction(timestamp);
|
||||
|
||||
// The following block of code effectively implements `FindEdge` and
|
||||
// yields an accessor that is only valid for managing the edge's
|
||||
@@ -550,31 +550,6 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!");
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::SCHEMA_CREATE: {
|
||||
// std::stringstream ss;
|
||||
// utils::PrintIterable(ss, delta.operation_label_create_schema);
|
||||
// spdlog::trace(" Create schema on label :{}", delta.operation_label_create_schema.label, ss.str());
|
||||
// if (commit_timestamp_and_accessor) {
|
||||
// throw utils::BasicException("Invalid transaction!");
|
||||
// }
|
||||
// if (!storage_->CreateSchema(storage_->NameToLabel(delta.operation_label_create_schema.label),
|
||||
// delta.operation_label_create_schema.schema_properties_types, timestamp)) {
|
||||
// throw utils::BasicException("Invalid transaction!");
|
||||
// }
|
||||
break;
|
||||
}
|
||||
case durability::WalDeltaData::Type::SCHEMA_DROP: {
|
||||
// std::stringstream ss;
|
||||
// utils::PrintIterable(ss, delta.operation_label);
|
||||
// spdlog::trace(" Drop schema on label :{}", delta.operation_label.label, ss.str());
|
||||
// if (commit_timestamp_and_accessor) {
|
||||
// throw utils::BasicException("Invalid transaction!");
|
||||
// }
|
||||
// if (!storage_->DropSchema(storage_->NameToLabel(delta.operation_label.label), timestamp)) {
|
||||
// throw utils::BasicException("Invalid transaction!");
|
||||
// }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ class Encoder final : public durability::BaseEncoder {
|
||||
|
||||
void WriteUint(uint64_t value) override;
|
||||
|
||||
// void WriteUint(uint8_t value) override;
|
||||
|
||||
void WriteDouble(double value) override;
|
||||
|
||||
void WriteString(const std::string_view &value) override;
|
||||
|
||||
@@ -1,83 +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 <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label) : status{status}, label{label} {}
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaPropertyType violated_type)
|
||||
: status{status}, label{label}, violated_type{violated_type} {}
|
||||
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaPropertyType violated_type,
|
||||
PropertyValue violated_property_value)
|
||||
: status{status}, label{label}, violated_type{violated_type}, violated_property_value{violated_property_value} {}
|
||||
|
||||
Schemas::SchemasList Schemas::ListSchemas() const {
|
||||
Schemas::SchemasList ret;
|
||||
ret.reserve(schemas_.size());
|
||||
for (const auto &[label_props, schema_property] : schemas_) {
|
||||
ret.emplace_back(label_props, schema_property);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<Schemas::Schema> Schemas::GetSchema(const LabelId primary_label) const {
|
||||
if (auto schema_map = schemas_.find(primary_label); schema_map != schemas_.end()) {
|
||||
return Schema{schema_map->first, schema_map->second};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<SchemaPropertyType> &schemas_types) {
|
||||
if (schemas_.contains(primary_label)) {
|
||||
return false;
|
||||
}
|
||||
schemas_.insert({primary_label, schemas_types});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Schemas::DropSchema(const LabelId primary_label) { return schemas_.erase(primary_label); }
|
||||
|
||||
std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_label, const Vertex &vertex) {
|
||||
// TODO Check for multiple defined primary labels
|
||||
const auto schema = schemas_.find(primary_label);
|
||||
if (schema == schemas_.end()) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL, primary_label);
|
||||
}
|
||||
if (!utils::Contains(vertex.labels, primary_label)) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_LABEL, primary_label);
|
||||
}
|
||||
|
||||
for (const auto &schema_type : schema->second) {
|
||||
if (!vertex.properties.HasProperty(schema_type.property_id)) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PROPERTY, primary_label, schema_type);
|
||||
}
|
||||
// Property type check
|
||||
// TODO Can this be replaced with just property id check?
|
||||
if (auto vertex_property = vertex.properties.GetProperty(schema_type.property_id);
|
||||
PropertyTypeToSchemaType(vertex_property) != schema_type.type) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
|
||||
vertex_property);
|
||||
}
|
||||
}
|
||||
// TODO after the introduction of vertex hashing introduce check for vertex
|
||||
// primary key uniqueness
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
@@ -1,156 +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 <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/temporal.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
class SchemaViolationException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
struct SchemaPropertyType {
|
||||
common::SchemaType type;
|
||||
PropertyId property_id;
|
||||
};
|
||||
|
||||
struct SchemaViolation {
|
||||
enum class ValidationStatus : uint8_t {
|
||||
VERTEX_HAS_NO_PRIMARY_LABEL,
|
||||
VERTEX_HAS_NO_PROPERTY,
|
||||
NO_SCHEMA_DEFINED_FOR_LABEL,
|
||||
VERTEX_PROPERTY_WRONG_TYPE
|
||||
};
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label);
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label, SchemaPropertyType violated_type);
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label, SchemaPropertyType violated_type,
|
||||
PropertyValue violated_property_value);
|
||||
|
||||
ValidationStatus status;
|
||||
LabelId label;
|
||||
std::optional<SchemaPropertyType> violated_type;
|
||||
std::optional<PropertyValue> violated_property_value;
|
||||
};
|
||||
|
||||
/// Structure that represents a collection of schemas
|
||||
/// Schema can be mapped under only one label => primary label
|
||||
class Schemas {
|
||||
public:
|
||||
using Schema = std::pair<LabelId, std::vector<SchemaPropertyType>>;
|
||||
using SchemasMap = std::unordered_map<LabelId, std::vector<SchemaPropertyType>>;
|
||||
using SchemasList = std::vector<Schema>;
|
||||
|
||||
Schemas() = default;
|
||||
Schemas(const Schemas &) = delete;
|
||||
Schemas(Schemas &&) = delete;
|
||||
Schemas &operator=(const Schemas &) = delete;
|
||||
Schemas &operator=(Schemas &&) = delete;
|
||||
~Schemas() = default;
|
||||
|
||||
[[nodiscard]] SchemasList ListSchemas() const;
|
||||
|
||||
[[nodiscard]] std::optional<Schemas::Schema> GetSchema(LabelId primary_label) const;
|
||||
|
||||
// Returns true if it was successfully created or false if the schema
|
||||
// already exists
|
||||
[[nodiscard]] bool CreateSchema(LabelId label, const std::vector<SchemaPropertyType> &schemas_types);
|
||||
|
||||
// Returns true if it was successfully dropped or false if the schema
|
||||
// does not exist
|
||||
[[nodiscard]] bool DropSchema(LabelId label);
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateVertex(LabelId primary_label, const Vertex &vertex);
|
||||
|
||||
private:
|
||||
SchemasMap schemas_;
|
||||
};
|
||||
|
||||
inline std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value) {
|
||||
switch (property_value.type()) {
|
||||
case PropertyValue::Type::Bool: {
|
||||
return common::SchemaType::BOOL;
|
||||
}
|
||||
case PropertyValue::Type::Int: {
|
||||
return common::SchemaType::INT;
|
||||
}
|
||||
case PropertyValue::Type::String: {
|
||||
return common::SchemaType::STRING;
|
||||
}
|
||||
case PropertyValue::Type::TemporalData: {
|
||||
switch (property_value.ValueTemporalData().type) {
|
||||
case TemporalType::Date: {
|
||||
return common::SchemaType::DATE;
|
||||
}
|
||||
case TemporalType::LocalDateTime: {
|
||||
return common::SchemaType::LOCALDATETIME;
|
||||
}
|
||||
case TemporalType::LocalTime: {
|
||||
return common::SchemaType::LOCALTIME;
|
||||
}
|
||||
case TemporalType::Duration: {
|
||||
return common::SchemaType::DURATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
case PropertyValue::Type::Double:
|
||||
case PropertyValue::Type::Null:
|
||||
case PropertyValue::Type::Map:
|
||||
case PropertyValue::Type::List: {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string SchemaTypeToString(const common::SchemaType type) {
|
||||
switch (type) {
|
||||
case common::SchemaType::BOOL: {
|
||||
return "Bool";
|
||||
}
|
||||
case common::SchemaType::INT: {
|
||||
return "Integer";
|
||||
}
|
||||
case common::SchemaType::STRING: {
|
||||
return "String";
|
||||
}
|
||||
case common::SchemaType::DATE: {
|
||||
return "Date";
|
||||
}
|
||||
case common::SchemaType::LOCALTIME: {
|
||||
return "LocalTime";
|
||||
}
|
||||
case common::SchemaType::LOCALDATETIME: {
|
||||
return "LocalDateTime";
|
||||
}
|
||||
case common::SchemaType::DURATION: {
|
||||
return "Duration";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/replication/config.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/file.hpp"
|
||||
@@ -38,7 +37,6 @@
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/stat.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
/// REPLICATION ///
|
||||
@@ -458,13 +456,12 @@ VertexAccessor Storage::Accessor::CreateVertex() {
|
||||
OOMExceptionEnabler oom_exception;
|
||||
auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
|
||||
auto acc = storage_->vertices_.access();
|
||||
auto *delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta});
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
|
||||
delta->prev.Set(&*it);
|
||||
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
|
||||
return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_);
|
||||
}
|
||||
|
||||
VertexAccessor Storage::Accessor::CreateVertex(storage::Gid gid) {
|
||||
@@ -1230,49 +1227,6 @@ ConstraintsInfo Storage::ListAllConstraints() const {
|
||||
return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()};
|
||||
}
|
||||
|
||||
SchemasInfo Storage::ListAllSchemas() const {
|
||||
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
|
||||
return {schemas_.ListSchemas()};
|
||||
}
|
||||
|
||||
SchemasInfo Storage::GetSchema(const LabelId primary_label) const {
|
||||
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
|
||||
if (const auto schema = schemas_.GetSchema(primary_label); schema) {
|
||||
return {{*schema}};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Storage::CreateSchema(const LabelId primary_label, const std::vector<SchemaPropertyType> &schemas_types,
|
||||
const std::optional<uint64_t> desired_commit_timestamp) {
|
||||
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||
auto ret = schemas_.CreateSchema(primary_label, schemas_types);
|
||||
if (!ret) {
|
||||
return ret;
|
||||
}
|
||||
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
|
||||
AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, *schemas_.GetSchema(primary_label),
|
||||
commit_timestamp);
|
||||
commit_log_->MarkFinished(commit_timestamp);
|
||||
last_commit_timestamp_ = commit_timestamp;
|
||||
|
||||
return schemas_.CreateSchema(primary_label, schemas_types);
|
||||
}
|
||||
|
||||
bool Storage::DropSchema(const LabelId primary_label, std::optional<uint64_t> desired_commit_timestamp) {
|
||||
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
|
||||
auto res = schemas_.DropSchema(primary_label);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
|
||||
AppendToWal(durability::StorageGlobalOperation::SCHEMA_DROP, primary_label, {}, commit_timestamp);
|
||||
commit_log_->MarkFinished(commit_timestamp);
|
||||
last_commit_timestamp_ = commit_timestamp;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
StorageInfo Storage::GetInfo() const {
|
||||
auto vertex_count = vertices_.size();
|
||||
auto edge_count = edge_count_.load(std::memory_order_acquire);
|
||||
@@ -1815,25 +1769,6 @@ void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId
|
||||
FinalizeWalFile();
|
||||
}
|
||||
|
||||
void Storage::AppendToWal(durability::StorageGlobalOperation operation, const Schemas::Schema &schema,
|
||||
uint64_t final_commit_timestamp) {
|
||||
if (!InitializeWalFile()) return;
|
||||
wal_file_->AppendOperation(operation, schema, final_commit_timestamp);
|
||||
{
|
||||
if (replication_role_.load() == ReplicationRole::MAIN) {
|
||||
replication_clients_.WithLock([&](auto &clients) {
|
||||
for (auto &client : clients) {
|
||||
client->StartTransactionReplication(wal_file_->SequenceNumber());
|
||||
client->IfStreamingTransaction(
|
||||
[&](auto &stream) { stream.AppendOperation(operation, schema, final_commit_timestamp); });
|
||||
client->FinalizeTransactionReplication();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
FinalizeWalFile();
|
||||
}
|
||||
|
||||
utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot() {
|
||||
if (replication_role_.load() != ReplicationRole::MAIN) {
|
||||
return CreateSnapshotError::DisabledForReplica;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "storage/v2/commit_log.hpp"
|
||||
@@ -26,18 +25,14 @@
|
||||
#include "storage/v2/durability/wal.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/schemas.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
@@ -178,11 +173,6 @@ struct ConstraintsInfo {
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing schemas in the storage
|
||||
struct SchemasInfo {
|
||||
Schemas::SchemasList schemas;
|
||||
};
|
||||
|
||||
/// Structure used to return information about the storage.
|
||||
struct StorageInfo {
|
||||
uint64_t vertex_count;
|
||||
@@ -374,7 +364,7 @@ class Storage final {
|
||||
IndicesInfo ListAllIndices() const;
|
||||
|
||||
/// Creates an existence constraint. Returns true if the constraint was
|
||||
/// successfully added, false if it already exists and a `ConstraintViolation`
|
||||
/// successfuly added, false if it already exists and a `ConstraintViolation`
|
||||
/// if there is an existing vertex violating the constraint.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
@@ -412,15 +402,6 @@ class Storage final {
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const;
|
||||
|
||||
SchemasInfo GetSchema(LabelId primary_label) const;
|
||||
|
||||
bool CreateSchema(LabelId primary_label, const std::vector<SchemaPropertyType> &schemas_types,
|
||||
std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
bool DropSchema(LabelId primary_label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
StorageInfo GetInfo() const;
|
||||
|
||||
bool LockPath();
|
||||
@@ -485,8 +466,6 @@ class Storage final {
|
||||
void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp);
|
||||
void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
|
||||
uint64_t final_commit_timestamp);
|
||||
void AppendToWal(durability::StorageGlobalOperation operation, const Schemas::Schema &schema,
|
||||
uint64_t final_commit_timestamp);
|
||||
|
||||
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
|
||||
@@ -512,7 +491,6 @@ class Storage final {
|
||||
|
||||
Constraints constraints_;
|
||||
Indices indices_;
|
||||
Schemas schemas_;
|
||||
|
||||
// Transaction engine
|
||||
utils::SpinLock engine_lock_;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Set up C++ functions for e2e tests
|
||||
function(add_query_module target_name src)
|
||||
add_library(${target_name} SHARED ${src})
|
||||
SET_TARGET_PROPERTIES(${target_name} PROPERTIES PREFIX "")
|
||||
target_include_directories(${target_name} PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
endfunction()
|
||||
|
||||
|
||||
function(copy_e2e_python_files TARGET_PREFIX FILE_NAME)
|
||||
add_custom_target(memgraph__e2e__${TARGET_PREFIX}__${FILE_NAME} ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
@@ -22,7 +14,6 @@ add_custom_target(memgraph__e2e__${TARGET_PREFIX}__${FILE_NAME} ALL
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(server)
|
||||
add_subdirectory(replication)
|
||||
add_subdirectory(memory)
|
||||
add_subdirectory(triggers)
|
||||
@@ -32,8 +23,6 @@ add_subdirectory(temporal_types)
|
||||
add_subdirectory(write_procedures)
|
||||
add_subdirectory(magic_functions)
|
||||
add_subdirectory(module_file_manager)
|
||||
add_subdirectory(monitoring_server)
|
||||
add_subdirectory(websocket)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.key DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# Set up C++ functions for e2e tests
|
||||
function(add_query_module target_name src)
|
||||
add_library(${target_name} SHARED ${src})
|
||||
SET_TARGET_PROPERTIES(${target_name} PROPERTIES PREFIX "")
|
||||
target_include_directories(${target_name} PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
endfunction()
|
||||
|
||||
# Set up Python functions for e2e tests
|
||||
function(copy_magic_functions_e2e_python_files FILE_NAME)
|
||||
copy_e2e_python_files(functions ${FILE_NAME})
|
||||
|
||||
@@ -21,13 +21,13 @@ static void ReturnFunctionArgument(struct mgp_list *args, mgp_func_context *ctx,
|
||||
struct mgp_memory *memory) {
|
||||
mgp_value *value{nullptr};
|
||||
auto err_code = mgp_list_at(args, 0, &value);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
|
||||
return;
|
||||
}
|
||||
|
||||
err_code = mgp_func_result_set_value(result, value, memory);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
|
||||
return;
|
||||
}
|
||||
@@ -37,13 +37,13 @@ static void ReturnOptionalArgument(struct mgp_list *args, mgp_func_context *ctx,
|
||||
struct mgp_memory *memory) {
|
||||
mgp_value *value{nullptr};
|
||||
auto err_code = mgp_list_at(args, 0, &value);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
|
||||
return;
|
||||
}
|
||||
|
||||
err_code = mgp_func_result_set_value(result, value, memory);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ static void ReturnOptionalArgument(struct mgp_list *args, mgp_func_context *ctx,
|
||||
|
||||
double GetElementFromArg(struct mgp_list *args, int index) {
|
||||
mgp_value *value{nullptr};
|
||||
if (mgp_list_at(args, index, &value) != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (mgp_list_at(args, index, &value) != MGP_ERROR_NO_ERROR) {
|
||||
throw std::runtime_error("Error while argument fetching.");
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ static void AddTwoNumbers(struct mgp_list *args, mgp_func_context *ctx, mgp_func
|
||||
memgraph::utils::OnScopeExit delete_summation_value([&value] { mgp_value_destroy(value); });
|
||||
|
||||
auto err_code = mgp_func_result_set_value(result, value, memory);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ static void ReturnNull(struct mgp_list *args, mgp_func_context *ctx, mgp_func_re
|
||||
memgraph::utils::OnScopeExit delete_null([&value] { mgp_value_destroy(value); });
|
||||
|
||||
auto err_code = mgp_func_result_set_value(result, value, memory);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
|
||||
}
|
||||
}
|
||||
@@ -111,14 +111,14 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
{
|
||||
mgp_func *func{nullptr};
|
||||
auto err_code = mgp_module_add_function(module, "return_function_argument", ReturnFunctionArgument, &func);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgp_type *type_any{nullptr};
|
||||
mgp_type_any(&type_any);
|
||||
err_code = mgp_func_add_arg(func, "argument", type_any);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
{
|
||||
mgp_func *func{nullptr};
|
||||
auto err_code = mgp_module_add_function(module, "return_optional_argument", ReturnOptionalArgument, &func);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
mgp_type *type_int{nullptr};
|
||||
mgp_type_int(&type_int);
|
||||
err_code = mgp_func_add_opt_arg(func, "opt_argument", type_int, default_value);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -145,18 +145,18 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
{
|
||||
mgp_func *func{nullptr};
|
||||
auto err_code = mgp_module_add_function(module, "add_two_numbers", AddTwoNumbers, &func);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgp_type *type_number{nullptr};
|
||||
mgp_type_number(&type_number);
|
||||
err_code = mgp_func_add_arg(func, "first", type_number);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
err_code = mgp_func_add_arg(func, "second", type_number);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
{
|
||||
mgp_func *func{nullptr};
|
||||
auto err_code = mgp_module_add_function(module, "return_null", ReturnNull, &func);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ static void TryToWrite(struct mgp_list *args, mgp_func_context *ctx, mgp_func_re
|
||||
|
||||
// Setting a property should set an error
|
||||
auto err_code = mgp_vertex_set_property(vertex, name, value);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Cannot set property in the function!", memory);
|
||||
return;
|
||||
}
|
||||
|
||||
err_code = mgp_func_result_set_value(result, value, memory);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
|
||||
return;
|
||||
}
|
||||
@@ -44,21 +44,21 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
{
|
||||
mgp_func *func{nullptr};
|
||||
auto err_code = mgp_module_add_function(module, "try_to_write", TryToWrite, &func);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgp_type *type_vertex{nullptr};
|
||||
mgp_type_node(&type_vertex);
|
||||
err_code = mgp_func_add_arg(func, "argument", type_vertex);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
mgp_type *type_string{nullptr};
|
||||
mgp_type_string(&type_string);
|
||||
err_code = mgp_func_add_arg(func, "name", type_string);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
mgp_type *nullable_type{nullptr};
|
||||
mgp_type_nullable(any_type, &nullable_type);
|
||||
err_code = mgp_func_add_arg(func, "value", nullable_type);
|
||||
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
|
||||
if (err_code != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,7 @@
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import copy
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import mgclient
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", ".."))
|
||||
@@ -24,18 +17,6 @@ BUILD_DIR = os.path.join(PROJECT_DIR, "build")
|
||||
MEMGRAPH_BINARY = os.path.join(BUILD_DIR, "memgraph")
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.01):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
count = 0
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
if count > 10 / 0.01:
|
||||
print("Could not wait for server on port", port, "to startup!")
|
||||
sys.exit(1)
|
||||
count += 1
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def extract_bolt_port(args):
|
||||
for arg_index, arg in enumerate(args):
|
||||
if arg.startswith("--bolt-port="):
|
||||
@@ -53,56 +34,3 @@ def extract_bolt_port(args):
|
||||
|
||||
def replace_paths(path):
|
||||
return path.replace("$PROJECT_DIR", PROJECT_DIR).replace("$SCRIPT_DIR", SCRIPT_DIR).replace("$BUILD_DIR", BUILD_DIR)
|
||||
|
||||
|
||||
class MemgraphInstanceRunner:
|
||||
def __init__(self, binary_path=MEMGRAPH_BINARY, use_ssl=False):
|
||||
self.host = "127.0.0.1"
|
||||
self.bolt_port = None
|
||||
self.binary_path = binary_path
|
||||
self.args = None
|
||||
self.proc_mg = None
|
||||
self.conn = None
|
||||
self.ssl = use_ssl
|
||||
|
||||
def query(self, query):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute(query)
|
||||
return cursor.fetchall()
|
||||
|
||||
def start(self, restart=False, args=[]):
|
||||
if not restart and self.is_running():
|
||||
return
|
||||
self.stop()
|
||||
self.args = copy.deepcopy(args)
|
||||
self.args = [replace_paths(arg) for arg in self.args]
|
||||
self.data_directory = tempfile.TemporaryDirectory()
|
||||
args_mg = [
|
||||
self.binary_path,
|
||||
"--data-directory",
|
||||
self.data_directory.name,
|
||||
"--storage-wal-enabled",
|
||||
"--storage-snapshot-interval-sec",
|
||||
"300",
|
||||
"--storage-properties-on-edges",
|
||||
] + self.args
|
||||
self.bolt_port = extract_bolt_port(args_mg)
|
||||
self.proc_mg = subprocess.Popen(args_mg)
|
||||
wait_for_server(self.bolt_port)
|
||||
self.conn = mgclient.connect(host=self.host, port=self.bolt_port, sslmode=self.ssl)
|
||||
self.conn.autocommit = True
|
||||
assert self.is_running(), "The Memgraph process died!"
|
||||
|
||||
def is_running(self):
|
||||
if self.proc_mg is None:
|
||||
return False
|
||||
if self.proc_mg.poll() is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
if not self.is_running():
|
||||
return
|
||||
self.proc_mg.terminate()
|
||||
code = self.proc_mg.wait()
|
||||
assert code == 0, "The Memgraph process exited with non-zero!"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_executable(memgraph__e2e__monitoring_server monitoring.cpp)
|
||||
target_link_libraries(memgraph__e2e__monitoring_server mgclient mg-utils json gflags Boost::headers)
|
||||
|
||||
add_executable(memgraph__e2e__monitoring_server_ssl monitoring_ssl.cpp)
|
||||
target_link_libraries(memgraph__e2e__monitoring_server_ssl mgclient mg-utils json gflags Boost::headers)
|
||||
@@ -18,7 +18,7 @@ from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from memgraph import MemgraphInstanceRunner
|
||||
from gqlalchemy import MemgraphInstanceBinary
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", ".."))
|
||||
@@ -63,7 +63,7 @@ def run(args):
|
||||
if "ssl" in config:
|
||||
use_ssl = bool(config["ssl"])
|
||||
config.pop("ssl")
|
||||
mg_instance = MemgraphInstanceRunner(MEMGRAPH_BINARY, use_ssl)
|
||||
mg_instance = MemgraphInstanceBinary(MEMGRAPH_BINARY, use_ssl)
|
||||
mg_instances[name] = mg_instance
|
||||
log_file_path = os.path.join(BUILD_DIR, "logs", config["log_file"])
|
||||
binary_args = config["args"] + ["--log-file", log_file_path]
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_executable(memgraph__e2e__server_connection server_connection.cpp)
|
||||
target_link_libraries(memgraph__e2e__server_connection mgclient mg-utils gflags)
|
||||
|
||||
add_executable(memgraph__e2e__server_ssl_connection server_ssl_connection.cpp)
|
||||
target_link_libraries(memgraph__e2e__server_ssl_connection mgclient mg-utils gflags)
|
||||
@@ -1,60 +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 <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
#include <mgclient.hpp>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
inline void OnTimeoutExpiration(const boost::system::error_code &ec) {
|
||||
// Timer was not cancelled, take necessary action.
|
||||
MG_ASSERT(!!ec, "Connection timeout");
|
||||
}
|
||||
|
||||
inline void EstablishConnection(const uint16_t bolt_port, const bool use_ssl) {
|
||||
spdlog::info("Testing successfull connection from one client");
|
||||
mg::Client::Init();
|
||||
|
||||
boost::asio::io_context ioc;
|
||||
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
|
||||
timer.async_wait(std::bind_front(&OnTimeoutExpiration));
|
||||
std::jthread bg_thread([&ioc]() { ioc.run(); });
|
||||
|
||||
auto client = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = use_ssl});
|
||||
MG_ASSERT(client, "Failed to connect!");
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
inline void EstablishMultipleConnections(const uint16_t bolt_port, const bool use_ssl) {
|
||||
spdlog::info("Testing successfull connection from multiple clients");
|
||||
mg::Client::Init();
|
||||
|
||||
boost::asio::io_context ioc;
|
||||
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
|
||||
timer.async_wait(std::bind_front(&OnTimeoutExpiration));
|
||||
std::jthread bg_thread([&ioc]() { ioc.run(); });
|
||||
|
||||
auto client1 = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = use_ssl});
|
||||
auto client2 = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = use_ssl});
|
||||
auto client3 = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = use_ssl});
|
||||
|
||||
MG_ASSERT(client1, "Failed to connect!");
|
||||
MG_ASSERT(client2, "Failed to connect!");
|
||||
MG_ASSERT(client3, "Failed to connect!");
|
||||
timer.cancel();
|
||||
}
|
||||
@@ -1,56 +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 <unistd.h>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
#include <mgclient.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_uint64(bolt_port, 7687, "Bolt port");
|
||||
|
||||
void EstablishSSLConnectionToNonSSLServer(const auto bolt_port) {
|
||||
spdlog::info("Testing that connection fails when connecting to non SSL server while using SSL");
|
||||
mg::Client::Init();
|
||||
|
||||
boost::asio::io_context ioc;
|
||||
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
|
||||
timer.async_wait(std::bind_front(&OnTimeoutExpiration));
|
||||
std::jthread bg_thread([&ioc]() { ioc.run(); });
|
||||
|
||||
auto client = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = true});
|
||||
|
||||
MG_ASSERT(client == nullptr, "Connection not refused when connecting with SSL turned on to a non SSL server!");
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage("Memgraph E2E server connection!");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
MG_ASSERT(FLAGS_bolt_port != 0);
|
||||
memgraph::logging::RedirectToStderr();
|
||||
|
||||
const auto bolt_port = static_cast<uint16_t>(FLAGS_bolt_port);
|
||||
|
||||
EstablishConnection(bolt_port, false);
|
||||
EstablishMultipleConnections(bolt_port, false);
|
||||
EstablishSSLConnectionToNonSSLServer(bolt_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,57 +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 <unistd.h>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <thread>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
#include <mgclient.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_uint64(bolt_port, 7687, "Bolt port");
|
||||
|
||||
void EstablishNonSSLConnectionToSSLServer(const auto bolt_port) {
|
||||
spdlog::info("Testing that connection fails when connecting to SSL server without using SSL");
|
||||
mg::Client::Init();
|
||||
|
||||
boost::asio::io_context ioc;
|
||||
boost::asio::steady_timer timer(ioc, std::chrono::seconds(5));
|
||||
timer.async_wait(std::bind_front(&OnTimeoutExpiration));
|
||||
std::jthread bg_thread([&ioc]() { ioc.run(); });
|
||||
|
||||
auto client = mg::Client::Connect({.host = "127.0.0.1", .port = bolt_port, .use_ssl = false});
|
||||
|
||||
MG_ASSERT(client == nullptr, "Connection not refused when conneting without SSL turned on to a SSL server!");
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
google::SetUsageMessage("Memgraph E2E server SSL connection!");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
MG_ASSERT(FLAGS_bolt_port != 0);
|
||||
memgraph::logging::RedirectToStderr();
|
||||
|
||||
const auto bolt_port = static_cast<uint16_t>(FLAGS_bolt_port);
|
||||
|
||||
EstablishConnection(bolt_port, true);
|
||||
EstablishMultipleConnections(bolt_port, true);
|
||||
EstablishNonSSLConnectionToSSLServer(bolt_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
cert_file: &cert_file "$PROJECT_DIR/tests/e2e/memgraph-selfsigned.crt"
|
||||
key_file: &key_file "$PROJECT_DIR/tests/e2e/memgraph-selfsigned.key"
|
||||
bolt_port: &bolt_port "7687"
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
server:
|
||||
args: ["--bolt-port=7687", "--log-level=TRACE", "--"]
|
||||
log_file: "server-connection-e2e.log"
|
||||
template_cluster_ssl: &template_cluster_ssl
|
||||
cluster:
|
||||
server:
|
||||
args:
|
||||
[
|
||||
"--bolt-port",
|
||||
*bolt_port,
|
||||
"--log-level=TRACE",
|
||||
"--bolt-cert-file",
|
||||
*cert_file,
|
||||
"--bolt-key-file",
|
||||
*key_file,
|
||||
"--",
|
||||
]
|
||||
log_file: "server-connection-ssl-e2e.log"
|
||||
ssl: true
|
||||
|
||||
workloads:
|
||||
- name: "Server connection"
|
||||
binary: "tests/e2e/server/memgraph__e2e__server_connection"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *template_cluster
|
||||
- name: "Server SSL connection"
|
||||
binary: "tests/e2e/server/memgraph__e2e__server_ssl_connection"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *template_cluster_ssl
|
||||
@@ -1,8 +0,0 @@
|
||||
There are three docker-compose files in this directory:
|
||||
* [kafka.yml](kafka.yml)
|
||||
* [pulsar.yml](pulsar.yml)
|
||||
* [redpanda.yml](redpanda.yml)
|
||||
|
||||
To run one of them, use the `docker-compose -f <filename> up -V` command. Optionally you can append `-d` to detach from the started containers. You can stop the detach containers by `docker-compose -f <filename> down`.
|
||||
|
||||
If you experience strange errors, try to clean up the previously created containers by `docker-compose -f <filename> rm -svf`.
|
||||
@@ -1,13 +1,13 @@
|
||||
version: '3.7'
|
||||
version: "3"
|
||||
services:
|
||||
zookeeper:
|
||||
image: 'bitnami/zookeeper:latest'
|
||||
image: 'bitnami/zookeeper:3.6.3-debian-10-r33'
|
||||
ports:
|
||||
- '2181:2181'
|
||||
environment:
|
||||
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||
kafka:
|
||||
image: 'bitnami/kafka:latest'
|
||||
image: 'bitnami/kafka:2.8.0-debian-10-r49'
|
||||
ports:
|
||||
- '9092:9092'
|
||||
environment:
|
||||
@@ -18,3 +18,9 @@ services:
|
||||
- ALLOW_PLAINTEXT_LISTENER=yes
|
||||
depends_on:
|
||||
- zookeeper
|
||||
pulsar:
|
||||
image: 'apachepulsar/pulsar:2.8.1'
|
||||
ports:
|
||||
- '6652:8080'
|
||||
- '6650:6650'
|
||||
entrypoint: ['bin/pulsar', 'standalone']
|
||||
@@ -18,14 +18,12 @@ import time
|
||||
from multiprocessing import Process, Value
|
||||
import common
|
||||
|
||||
TRANSFORMATIONS_TO_CHECK_C = [
|
||||
"empty_transformation"]
|
||||
|
||||
TRANSFORMATIONS_TO_CHECK_PY = [
|
||||
TRANSFORMATIONS_TO_CHECK = [
|
||||
"kafka_transform.simple",
|
||||
"kafka_transform.with_parameters"]
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_simple(kafka_producer, kafka_topics, connection, transformation):
|
||||
assert len(kafka_topics) > 0
|
||||
cursor = connection.cursor()
|
||||
@@ -46,7 +44,7 @@ def test_simple(kafka_producer, kafka_topics, connection, transformation):
|
||||
cursor, topic, common.SIMPLE_MSG)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_separate_consumers(
|
||||
kafka_producer,
|
||||
kafka_topics,
|
||||
@@ -127,7 +125,7 @@ def test_start_from_last_committed_offset(
|
||||
cursor, kafka_topics[0], message)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_check_stream(
|
||||
kafka_producer,
|
||||
kafka_topics,
|
||||
@@ -309,7 +307,7 @@ def test_restart_after_error(kafka_producer, kafka_topics, connection):
|
||||
cursor, "MATCH (n:VERTEX { id : 42 }) RETURN n")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_bootstrap_server(
|
||||
kafka_producer,
|
||||
kafka_topics,
|
||||
@@ -336,7 +334,7 @@ def test_bootstrap_server(
|
||||
cursor, topic, common.SIMPLE_MSG)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_bootstrap_server_empty(
|
||||
kafka_producer,
|
||||
kafka_topics,
|
||||
@@ -354,7 +352,7 @@ def test_bootstrap_server_empty(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY)
|
||||
@pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK)
|
||||
def test_set_offset(kafka_producer, kafka_topics, connection, transformation):
|
||||
assert len(kafka_topics) > 0
|
||||
cursor = connection.cursor()
|
||||
@@ -452,14 +450,6 @@ def test_info_procedure(kafka_topics, connection):
|
||||
(local, configs, consumer_group, reducted_credentials, kafka_topics)]
|
||||
common.validate_info(stream_info, expected_stream_info)
|
||||
|
||||
@pytest.mark.parametrize("transformation",TRANSFORMATIONS_TO_CHECK_C)
|
||||
def test_load_c_transformations(connection, transformation):
|
||||
cursor = connection.cursor()
|
||||
query = "CALL mg.transformations() YIELD * WITH name WHERE name STARTS WITH 'c_transformations." + transformation + "' RETURN name"
|
||||
result = common.execute_and_fetch_all(
|
||||
cursor, query)
|
||||
assert len(result) == 1
|
||||
assert result[0][0] == "c_transformations." + transformation
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
pulsar:
|
||||
image: 'apachepulsar/pulsar:latest'
|
||||
ports:
|
||||
- '6652:8080'
|
||||
- '6650:6650'
|
||||
entrypoint: ['bin/pulsar', 'standalone']
|
||||
@@ -1,23 +0,0 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
redpanda:
|
||||
command:
|
||||
- redpanda
|
||||
- start
|
||||
- --smp
|
||||
- '1'
|
||||
- --reserve-memory
|
||||
- 0M
|
||||
- --overprovisioned
|
||||
- --node-id
|
||||
- '0'
|
||||
- --kafka-addr
|
||||
- PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092
|
||||
- --advertise-kafka-addr
|
||||
- PLAINTEXT://redpanda:29092,OUTSIDE://localhost:9092
|
||||
# NOTE: Please use the latest version here!
|
||||
image: docker.vectorized.io/vectorized/redpanda:latest
|
||||
container_name: redpanda-1
|
||||
ports:
|
||||
- 9092:9092
|
||||
- 29092:29092
|
||||
@@ -1,3 +1,2 @@
|
||||
copy_streams_e2e_python_files(kafka_transform.py)
|
||||
copy_streams_e2e_python_files(pulsar_transform.py)
|
||||
add_query_module(c_transformations c_transformations.cpp)
|
||||
|
||||
@@ -1,22 +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 "mg_procedure.h"
|
||||
|
||||
extern "C" int mgp_init_module(mgp_module *module, mgp_memory *memory) {
|
||||
static const auto no_op_cb = [](mgp_messages *msg, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {};
|
||||
|
||||
if (mgp_error::MGP_ERROR_NO_ERROR != mgp_module_add_transformation(module, "empty_transformation", no_op_cb)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
10
tests/e2e/websocket/CMakeLists.txt
Normal file
10
tests/e2e/websocket/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_executable(memgraph__e2e__websocket websocket.cpp)
|
||||
target_link_libraries(memgraph__e2e__websocket mgclient mg-utils json gflags Boost::headers)
|
||||
|
||||
add_executable(memgraph__e2e__websocket_ssl websocket_ssl.cpp)
|
||||
target_link_libraries(memgraph__e2e__websocket_ssl mgclient mg-utils json gflags Boost::headers)
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.key DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -1,15 +1,15 @@
|
||||
cert_file: &cert_file "$PROJECT_DIR/tests/e2e/memgraph-selfsigned.crt"
|
||||
key_file: &key_file "$PROJECT_DIR/tests/e2e/memgraph-selfsigned.key"
|
||||
cert_file: &cert_file "$PROJECT_DIR/tests/e2e/websocket/memgraph-selfsigned.crt"
|
||||
key_file: &key_file "$PROJECT_DIR/tests/e2e/websocket/memgraph-selfsigned.key"
|
||||
bolt_port: &bolt_port "7687"
|
||||
monitoring_port: &monitoring_port "7444"
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
monitoring:
|
||||
websocket:
|
||||
args: ["--bolt-port=7687", "--log-level=TRACE", "--"]
|
||||
log_file: "monitoring-websocket-e2e.log"
|
||||
log_file: "websocket-e2e.log"
|
||||
template_cluster_ssl: &template_cluster_ssl
|
||||
cluster:
|
||||
monitoring:
|
||||
websocket:
|
||||
args:
|
||||
[
|
||||
"--bolt-port",
|
||||
@@ -23,15 +23,16 @@ template_cluster_ssl: &template_cluster_ssl
|
||||
*key_file,
|
||||
"--",
|
||||
]
|
||||
log_file: "monitoring-websocket-ssl-e2e.log"
|
||||
log_file: "websocket-ssl-e2e.log"
|
||||
ssl: true
|
||||
|
||||
workloads:
|
||||
- name: "Monitoring server using WebSocket"
|
||||
binary: "tests/e2e/monitoring_server/memgraph__e2e__monitoring_server"
|
||||
- name: "Websocket"
|
||||
binary: "tests/e2e/websocket/memgraph__e2e__websocket"
|
||||
args: ["--bolt-port", *bolt_port, "--monitoring-port", *monitoring_port]
|
||||
<<: *template_cluster
|
||||
- name: "Monitoring server using WebSocket SSL"
|
||||
binary: "tests/e2e/monitoring_server/memgraph__e2e__monitoring_server_ssl"
|
||||
- name: "Websocket SSL"
|
||||
binary: "tests/e2e/websocket/memgraph__e2e__websocket_ssl"
|
||||
args: ["--bolt-port", *bolt_port, "--monitoring-port", *monitoring_port]
|
||||
<<: *template_cluster_ssl
|
||||
|
||||
@@ -20,9 +20,10 @@ import sys
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import yaml
|
||||
|
||||
from gqlalchemy import wait_for_port, MemgraphInstanceBinary
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
TESTS_DIR = os.path.join(SCRIPT_DIR, "tests")
|
||||
@@ -30,18 +31,6 @@ BASE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", ".."))
|
||||
BUILD_DIR = os.path.join(BASE_DIR, "build")
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.01):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
count = 0
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
if count > 20 / 0.01:
|
||||
print("Could not wait for server on port", port, "to startup!")
|
||||
sys.exit(1)
|
||||
count += 1
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def generate_result_csv(suite, result_path):
|
||||
if not os.path.exists(result_path):
|
||||
return ""
|
||||
@@ -87,41 +76,6 @@ def generate_result_html(data):
|
||||
return ret
|
||||
|
||||
|
||||
class MemgraphRunner():
|
||||
def __init__(self, build_directory):
|
||||
self.build_directory = build_directory
|
||||
self.proc_mg = None
|
||||
self.args = []
|
||||
|
||||
def start(self, args=[]):
|
||||
if args == self.args and self.is_running():
|
||||
return
|
||||
|
||||
self.stop()
|
||||
self.args = copy.deepcopy(args)
|
||||
|
||||
self.data_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_binary = os.path.join(self.build_directory, "memgraph")
|
||||
args_mg = [memgraph_binary, "--storage-properties-on-edges",
|
||||
"--data-directory", self.data_directory.name]
|
||||
self.proc_mg = subprocess.Popen(args_mg + self.args)
|
||||
wait_for_server(7687, 1)
|
||||
assert self.is_running(), "The Memgraph process died!"
|
||||
|
||||
def is_running(self):
|
||||
if self.proc_mg is None:
|
||||
return False
|
||||
if self.proc_mg.poll() is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
if not self.is_running():
|
||||
return
|
||||
self.proc_mg.terminate()
|
||||
code = self.proc_mg.wait()
|
||||
assert code == 0, "The Memgraph process exited with non-zero!"
|
||||
|
||||
|
||||
def main():
|
||||
# Parse args
|
||||
@@ -141,7 +95,7 @@ def main():
|
||||
output_dir = tempfile.TemporaryDirectory()
|
||||
|
||||
# Memgraph runner
|
||||
memgraph = MemgraphRunner(args.build_directory)
|
||||
memgraph = MemgraphInstanceBinary(args.build_directory)
|
||||
|
||||
@atexit.register
|
||||
def cleanup():
|
||||
|
||||
@@ -21,6 +21,8 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
@@ -58,13 +60,6 @@ QUERIES = [
|
||||
]
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_test(memgraph_binary, tester_binary):
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [
|
||||
@@ -80,7 +75,7 @@ def execute_test(memgraph_binary, tester_binary):
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
|
||||
@@ -19,6 +19,8 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
@@ -159,13 +161,6 @@ UNAUTHORIZED_ERROR = "You are not authorized to execute this query! Please " \
|
||||
"contact your database administrator."
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(binary, queries, should_fail=False, failure_message="",
|
||||
username="", password="", check_failure=True):
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
@@ -217,7 +212,7 @@ def execute_test(memgraph_binary, tester_binary, checker_binary):
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
@@ -248,7 +243,7 @@ def execute_test(memgraph_binary, tester_binary, checker_binary):
|
||||
admin_queries = ["REVOKE ALL PRIVILEGES FROM uSer"]
|
||||
if len(user_perms) > 0:
|
||||
admin_queries.append(
|
||||
"GRANT {} TO User".format(", ".join(user_perms)))
|
||||
"GRANT {} TO User".format(", ".join(user_perms)))
|
||||
execute_admin_queries(admin_queries)
|
||||
authorized, unauthorized = [], []
|
||||
for query, query_perms in QUERIES:
|
||||
|
||||
@@ -20,6 +20,8 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
@@ -32,15 +34,8 @@ DUMP_SNAPSHOT_FILE_NAME = "expected_snapshot.cypher"
|
||||
DUMP_WAL_FILE_NAME = "expected_wal.cypher"
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def sorted_content(file_path):
|
||||
with open(file_path, 'r') as fin:
|
||||
with open(file_path, "r") as fin:
|
||||
return sorted(list(map(lambda x: x.strip(), fin.readlines())))
|
||||
|
||||
|
||||
@@ -53,37 +48,41 @@ def list_to_string(data):
|
||||
|
||||
|
||||
def execute_test(
|
||||
memgraph_binary,
|
||||
dump_binary,
|
||||
test_directory,
|
||||
test_type,
|
||||
write_expected):
|
||||
assert test_type in ["SNAPSHOT", "WAL"], \
|
||||
"Test type should be either 'SNAPSHOT' or 'WAL'."
|
||||
print("\033[1;36m~~ Executing test {} ({}) ~~\033[0m"
|
||||
.format(os.path.relpath(test_directory, TESTS_DIR), test_type))
|
||||
memgraph_binary, dump_binary, test_directory, test_type, write_expected
|
||||
):
|
||||
assert test_type in [
|
||||
"SNAPSHOT",
|
||||
"WAL",
|
||||
], "Test type should be either 'SNAPSHOT' or 'WAL'."
|
||||
print(
|
||||
"\033[1;36m~~ Executing test {} ({}) ~~\033[0m".format(
|
||||
os.path.relpath(test_directory, TESTS_DIR), test_type
|
||||
)
|
||||
)
|
||||
|
||||
working_data_directory = tempfile.TemporaryDirectory()
|
||||
if test_type == "SNAPSHOT":
|
||||
snapshots_dir = os.path.join(working_data_directory.name, "snapshots")
|
||||
os.makedirs(snapshots_dir)
|
||||
shutil.copy(os.path.join(test_directory, SNAPSHOT_FILE_NAME),
|
||||
snapshots_dir)
|
||||
shutil.copy(os.path.join(test_directory, SNAPSHOT_FILE_NAME), snapshots_dir)
|
||||
else:
|
||||
wal_dir = os.path.join(working_data_directory.name, "wal")
|
||||
os.makedirs(wal_dir)
|
||||
shutil.copy(os.path.join(test_directory, WAL_FILE_NAME), wal_dir)
|
||||
|
||||
memgraph_args = [memgraph_binary,
|
||||
"--storage-recover-on-startup",
|
||||
"--storage-properties-on-edges",
|
||||
"--data-directory", working_data_directory.name]
|
||||
memgraph_args = [
|
||||
memgraph_binary,
|
||||
"--storage-recover-on-startup",
|
||||
"--storage-properties-on-edges",
|
||||
"--data-directory",
|
||||
working_data_directory.name,
|
||||
]
|
||||
|
||||
# Start the memgraph binary
|
||||
memgraph = subprocess.Popen(memgraph_args)
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687, delay=0.1)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
@@ -101,25 +100,30 @@ def execute_test(
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
dump_file_name = DUMP_SNAPSHOT_FILE_NAME if test_type == "SNAPSHOT" else DUMP_WAL_FILE_NAME
|
||||
dump_file_name = (
|
||||
DUMP_SNAPSHOT_FILE_NAME if test_type == "SNAPSHOT" else DUMP_WAL_FILE_NAME
|
||||
)
|
||||
|
||||
if write_expected:
|
||||
with open(dump_output_file.name, 'r') as dump:
|
||||
with open(dump_output_file.name, "r") as dump:
|
||||
queries_got = dump.readlines()
|
||||
# Write dump files
|
||||
expected_dump_file = os.path.join(test_directory, dump_file_name)
|
||||
with open(expected_dump_file, 'w') as expected:
|
||||
with open(expected_dump_file, "w") as expected:
|
||||
expected.writelines(queries_got)
|
||||
else:
|
||||
# Compare dump files
|
||||
expected_dump_file = os.path.join(test_directory, dump_file_name)
|
||||
assert os.path.exists(expected_dump_file), \
|
||||
"Could not find expected dump path {}".format(expected_dump_file)
|
||||
assert os.path.exists(
|
||||
expected_dump_file
|
||||
), "Could not find expected dump path {}".format(expected_dump_file)
|
||||
queries_got = sorted_content(dump_output_file.name)
|
||||
queries_expected = sorted_content(expected_dump_file)
|
||||
assert queries_got == queries_expected, "Expected\n{}\nto be equal to\n" \
|
||||
"{}".format(list_to_string(queries_got),
|
||||
list_to_string(queries_expected))
|
||||
assert (
|
||||
queries_got == queries_expected
|
||||
), "Expected\n{}\nto be equal to\n" "{}".format(
|
||||
list_to_string(queries_got), list_to_string(queries_expected)
|
||||
)
|
||||
|
||||
print("\033[1;32m~~ Test successful ~~\033[0m\n")
|
||||
|
||||
@@ -141,15 +145,19 @@ def find_test_directories(directory):
|
||||
continue
|
||||
snapshot_file = os.path.join(test_dir_path, SNAPSHOT_FILE_NAME)
|
||||
wal_file = os.path.join(test_dir_path, WAL_FILE_NAME)
|
||||
dump_snapshot_file = os.path.join(
|
||||
test_dir_path, DUMP_SNAPSHOT_FILE_NAME)
|
||||
dump_snapshot_file = os.path.join(test_dir_path, DUMP_SNAPSHOT_FILE_NAME)
|
||||
dump_wal_file = os.path.join(test_dir_path, DUMP_WAL_FILE_NAME)
|
||||
if (os.path.isfile(snapshot_file) and os.path.isfile(dump_snapshot_file)
|
||||
and os.path.isfile(wal_file) and os.path.isfile(dump_wal_file)):
|
||||
if (
|
||||
os.path.isfile(snapshot_file)
|
||||
and os.path.isfile(dump_snapshot_file)
|
||||
and os.path.isfile(wal_file)
|
||||
and os.path.isfile(dump_wal_file)
|
||||
):
|
||||
test_dirs.append(test_dir_path)
|
||||
else:
|
||||
raise Exception("Missing data in test directory '{}'"
|
||||
.format(test_dir_path))
|
||||
raise Exception(
|
||||
"Missing data in test directory '{}'".format(test_dir_path)
|
||||
)
|
||||
return test_dirs
|
||||
|
||||
|
||||
@@ -161,9 +169,10 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--dump", default=dump_binary)
|
||||
parser.add_argument(
|
||||
'--write-expected',
|
||||
action='store_true',
|
||||
help='Overwrite the expected cypher with results from current run')
|
||||
"--write-expected",
|
||||
action="store_true",
|
||||
help="Overwrite the expected cypher with results from current run",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
test_directories = find_test_directories(TESTS_DIR)
|
||||
@@ -171,16 +180,10 @@ if __name__ == "__main__":
|
||||
|
||||
for test_directory in test_directories:
|
||||
execute_test(
|
||||
args.memgraph,
|
||||
args.dump,
|
||||
test_directory,
|
||||
"SNAPSHOT",
|
||||
args.write_expected)
|
||||
args.memgraph, args.dump, test_directory, "SNAPSHOT", args.write_expected
|
||||
)
|
||||
execute_test(
|
||||
args.memgraph,
|
||||
args.dump,
|
||||
test_directory,
|
||||
"WAL",
|
||||
args.write_expected)
|
||||
args.memgraph, args.dump, test_directory, "WAL", args.write_expected
|
||||
)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
@@ -20,6 +20,8 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
@@ -45,13 +47,6 @@ roles:
|
||||
"""
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(binary, queries, username="", password="",
|
||||
auth_should_fail=False, query_should_fail=False):
|
||||
if password == "":
|
||||
@@ -121,7 +116,7 @@ class Memgraph:
|
||||
time.sleep(0.1)
|
||||
assert self._process.poll() is None, "Memgraph process died " \
|
||||
"prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
|
||||
def stop(self, check=True):
|
||||
if self._process is None:
|
||||
@@ -394,7 +389,7 @@ if __name__ == "__main__":
|
||||
slapd = subprocess.Popen(slapd_args)
|
||||
time.sleep(0.1)
|
||||
assert slapd.poll() is None, "slapd process died prematurely!"
|
||||
wait_for_server(1389)
|
||||
wait_for_port(port=1389)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
|
||||
@@ -20,19 +20,14 @@ import tempfile
|
||||
import time
|
||||
import yaml
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
BASE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
BUILD_DIR = os.path.join(BASE_DIR, "build")
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def extract_rows(data):
|
||||
return list(map(lambda x: x.strip(), data.strip().split("\n")))
|
||||
|
||||
@@ -60,7 +55,7 @@ def verify_lifetime(memgraph_binary, mg_import_csv_binary):
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
@@ -141,7 +136,7 @@ def execute_test(name, test_path, test_config, memgraph_binary,
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
from collections import defaultdict
|
||||
import tempfile
|
||||
import shutil
|
||||
import time
|
||||
from common import get_absolute_path, set_cpus
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
|
||||
try:
|
||||
import jail
|
||||
@@ -25,24 +24,18 @@ except:
|
||||
import jail_faker as jail
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", port]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
class Memgraph:
|
||||
"""
|
||||
Knows how to start and stop memgraph.
|
||||
"""
|
||||
|
||||
def __init__(self, args, num_workers):
|
||||
self.log = logging.getLogger("MemgraphRunner")
|
||||
argp = ArgumentParser("MemgraphArgumentParser")
|
||||
argp.add_argument("--runner-bin",
|
||||
default=get_absolute_path("memgraph", "build"))
|
||||
argp.add_argument("--port", default="7687",
|
||||
help="Database and client port")
|
||||
argp.add_argument(
|
||||
"--runner-bin", default=get_absolute_path("memgraph", "build")
|
||||
)
|
||||
argp.add_argument("--port", default="7687", help="Database and client port")
|
||||
argp.add_argument("--data-directory", default=None)
|
||||
argp.add_argument("--storage-snapshot-on-exit", action="store_true")
|
||||
argp.add_argument("--storage-recover-on-startup", action="store_true")
|
||||
@@ -55,8 +48,12 @@ class Memgraph:
|
||||
|
||||
def start(self):
|
||||
self.log.info("start")
|
||||
database_args = ["--bolt-port", self.args.port,
|
||||
"--query-execution-timeout-sec", "0"]
|
||||
database_args = [
|
||||
"--bolt-port",
|
||||
self.args.port,
|
||||
"--query-execution-timeout-sec",
|
||||
"0",
|
||||
]
|
||||
if self.num_workers:
|
||||
database_args += ["--bolt-num-workers", str(self.num_workers)]
|
||||
if self.args.data_directory:
|
||||
@@ -71,7 +68,7 @@ class Memgraph:
|
||||
|
||||
# start memgraph
|
||||
self.database_bin.run(runner_bin, database_args, timeout=600)
|
||||
wait_for_server(self.args.port)
|
||||
wait_for_port(port=self.args.port)
|
||||
|
||||
def stop(self):
|
||||
self.database_bin.send_signal(jail.SIGTERM)
|
||||
@@ -82,15 +79,17 @@ class Neo:
|
||||
"""
|
||||
Knows how to start and stop neo4j.
|
||||
"""
|
||||
|
||||
def __init__(self, args, config):
|
||||
self.log = logging.getLogger("NeoRunner")
|
||||
argp = ArgumentParser("NeoArgumentParser")
|
||||
argp.add_argument("--runner-bin", default=get_absolute_path(
|
||||
"neo4j/bin/neo4j", "libs"))
|
||||
argp.add_argument("--port", default="7687",
|
||||
help="Database and client port")
|
||||
argp.add_argument("--http-port", default="7474",
|
||||
help="Database and client port")
|
||||
argp.add_argument(
|
||||
"--runner-bin", default=get_absolute_path("neo4j/bin/neo4j", "libs")
|
||||
)
|
||||
argp.add_argument("--port", default="7687", help="Database and client port")
|
||||
argp.add_argument(
|
||||
"--http-port", default="7474", help="Database and client port"
|
||||
)
|
||||
self.log.info("Initializing Runner with arguments %r", args)
|
||||
self.args, _ = argp.parse_known_args(args)
|
||||
self.config = config
|
||||
@@ -105,29 +104,36 @@ class Neo:
|
||||
self.neo4j_home_path = tempfile.mkdtemp(dir="/dev/shm")
|
||||
|
||||
try:
|
||||
os.symlink(os.path.join(get_absolute_path("neo4j", "libs"), "lib"),
|
||||
os.path.join(self.neo4j_home_path, "lib"))
|
||||
os.symlink(
|
||||
os.path.join(get_absolute_path("neo4j", "libs"), "lib"),
|
||||
os.path.join(self.neo4j_home_path, "lib"),
|
||||
)
|
||||
neo4j_conf_dir = os.path.join(self.neo4j_home_path, "conf")
|
||||
neo4j_conf_file = os.path.join(neo4j_conf_dir, "neo4j.conf")
|
||||
os.mkdir(neo4j_conf_dir)
|
||||
shutil.copyfile(self.config, neo4j_conf_file)
|
||||
with open(neo4j_conf_file, "a") as f:
|
||||
f.write("\ndbms.connector.bolt.listen_address=:" +
|
||||
self.args.port + "\n")
|
||||
f.write("\ndbms.connector.http.listen_address=:" +
|
||||
self.args.http_port + "\n")
|
||||
f.write(
|
||||
"\ndbms.connector.bolt.listen_address=:" + self.args.port + "\n"
|
||||
)
|
||||
f.write(
|
||||
"\ndbms.connector.http.listen_address=:"
|
||||
+ self.args.http_port
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
# environment
|
||||
cwd = os.path.dirname(self.args.runner_bin)
|
||||
env = {"NEO4J_HOME": self.neo4j_home_path}
|
||||
|
||||
self.database_bin.run(self.args.runner_bin, args=["console"],
|
||||
env=env, timeout=600, cwd=cwd)
|
||||
self.database_bin.run(
|
||||
self.args.runner_bin, args=["console"], env=env, timeout=600, cwd=cwd
|
||||
)
|
||||
except:
|
||||
shutil.rmtree(self.neo4j_home_path)
|
||||
raise Exception("Couldn't run Neo4j!")
|
||||
|
||||
wait_for_server(self.args.http_port, 2.0)
|
||||
wait_for_port(port=self.args.http_port, delay=2.0)
|
||||
|
||||
def stop(self):
|
||||
self.database_bin.send_signal(jail.SIGTERM)
|
||||
|
||||
@@ -17,13 +17,7 @@ import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
def _convert_args_to_flags(*args, **kwargs):
|
||||
flags = list(args)
|
||||
@@ -92,7 +86,7 @@ class Memgraph:
|
||||
if self._proc_mg.poll() is not None:
|
||||
self._proc_mg = None
|
||||
raise Exception("The database process died prematurely!")
|
||||
wait_for_server(7687)
|
||||
wait_for_port(port=7687)
|
||||
ret = self._proc_mg.poll()
|
||||
assert ret is None, "The database process died prematurely " \
|
||||
"({})!".format(ret)
|
||||
|
||||
@@ -15,17 +15,12 @@ import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
BASE_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
|
||||
def wait_for_server(port, delay=1.0):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.5)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
class Memgraph:
|
||||
def __init__(self, dataset, port, num_workers):
|
||||
self.proc = None
|
||||
@@ -51,7 +46,7 @@ class Memgraph:
|
||||
|
||||
# start memgraph
|
||||
self.proc = subprocess.Popen(database_args, env=env)
|
||||
wait_for_server(self.port)
|
||||
wait_for_port(port=self.port, delay=1.0)
|
||||
|
||||
def stop(self):
|
||||
self.proc.terminate()
|
||||
@@ -99,7 +94,7 @@ class Neo:
|
||||
shutil.rmtree(self.home_dir)
|
||||
raise Exception("Couldn't run Neo4j!")
|
||||
|
||||
wait_for_server(self.http_port, 2.0)
|
||||
wait_for_port(port=self.http_port, delay=2.0)
|
||||
|
||||
def stop(self):
|
||||
self.proc.terminate()
|
||||
@@ -112,7 +107,7 @@ class Neo:
|
||||
|
||||
def parse_args():
|
||||
argp = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
argp.add_argument('--scale', type=int, default=1,
|
||||
help='Dataset scale to use for benchmarking.')
|
||||
argp.add_argument('--host', default='127.0.0.1', help='Database host.')
|
||||
@@ -196,11 +191,13 @@ def main():
|
||||
'-p', 'host', args.host, '-p', 'port', args.port,
|
||||
'-db', 'net.ellitron.ldbcsnbimpls.interactive.neo4j.Neo4jDb',
|
||||
'-p', 'ldbc.snb.interactive.parameters_dir', parameters_dir,
|
||||
'--time_compression_ratio', str(args.time_compression_ratio),
|
||||
'--time_compression_ratio', str(
|
||||
args.time_compression_ratio),
|
||||
'--operation_count', str(args.operation_count),
|
||||
'--thread_count', str(args.thread_count),
|
||||
'--time_unit', args.time_unit.upper())
|
||||
subprocess.check_call(java_cmd, cwd=os.path.join(SCRIPT_DIR, 'ldbc_driver'))
|
||||
subprocess.check_call(
|
||||
java_cmd, cwd=os.path.join(SCRIPT_DIR, 'ldbc_driver'))
|
||||
|
||||
# Copy the results to results dir.
|
||||
ldbc_results = os.path.join(SCRIPT_DIR, 'ldbc_driver', 'results',
|
||||
|
||||
@@ -6,12 +6,14 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PIP_DEPS=(
|
||||
"behave==1.2.6"
|
||||
"gqlalchemy==1.2.0"
|
||||
"ldap3==2.6"
|
||||
"kafka-python==2.0.2"
|
||||
"requests==2.25.1"
|
||||
"neo4j-driver==4.1.1"
|
||||
"parse==1.18.0"
|
||||
"parse-type==0.5.2"
|
||||
"pymgclient=1.2.0"
|
||||
"pytest==6.2.3"
|
||||
"pyyaml==5.4.1"
|
||||
"six==1.15.0"
|
||||
@@ -46,12 +48,4 @@ for pkg in "${PIP_DEPS[@]}"; do
|
||||
pip --timeout 1000 install "$pkg"
|
||||
done
|
||||
|
||||
# Install mgclient from source becasue of full flexibility.
|
||||
pushd "$DIR/../libs/pymgclient" > /dev/null
|
||||
export MGCLIENT_INCLUDE_DIR="$DIR/../libs/mgclient/include"
|
||||
export MGCLIENT_LIB_DIR="$DIR/../libs/mgclient/lib"
|
||||
CFLAGS="-std=c99" python3 setup.py build
|
||||
CFLAGS="-std=c99" python3 setup.py install
|
||||
popd > /dev/null
|
||||
|
||||
deactivate
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from gqlalchemy import wait_for_port
|
||||
|
||||
# dataset calibrated for running on Apollo (total 4min)
|
||||
# bipartite.py runs for approx. 30s
|
||||
# create_match.py runs for approx. 30s
|
||||
@@ -87,13 +87,6 @@ else:
|
||||
THREADS = multiprocessing.cpu_count()
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
# run test helper function
|
||||
def run_test(args, test, options, timeout):
|
||||
print("Running test '{}'".format(test))
|
||||
@@ -102,7 +95,7 @@ def run_test(args, test, options, timeout):
|
||||
if test.endswith(".py"):
|
||||
logging = "DEBUG" if args.verbose else "WARNING"
|
||||
binary = [args.python, "-u", os.path.join(SCRIPT_DIR, test),
|
||||
"--logging", logging]
|
||||
"--logging", logging]
|
||||
elif test.endswith(".cpp"):
|
||||
exe = os.path.join(BUILD_DIR, "tests", "stress", test[:-4])
|
||||
binary = [exe]
|
||||
@@ -112,11 +105,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))
|
||||
test, ret_test.returncode))
|
||||
|
||||
runtime = time.time() - start
|
||||
print(" Done after {:.3f} seconds".format(runtime))
|
||||
@@ -125,19 +118,19 @@ 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
|
||||
@@ -145,8 +138,8 @@ 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)
|
||||
"-days", "365", "-nodes", "-x509", "-subj", subj,
|
||||
"-keyout", KEY_FILE, "-out", CERT_FILE], check=True)
|
||||
|
||||
# start memgraph
|
||||
cwd = os.path.dirname(args.memgraph)
|
||||
@@ -166,18 +159,22 @@ 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)
|
||||
wait_for_server(7687)
|
||||
proc_mg = subprocess.Popen(cmd, cwd=cwd)
|
||||
wait_for_port(port=7687, delay=0.1)
|
||||
assert proc_mg.poll() is None, "The database binary died prematurely!"
|
||||
|
||||
# at exit cleanup
|
||||
|
||||
|
||||
@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
|
||||
|
||||
@@ -17,6 +17,7 @@ import time
|
||||
import threading
|
||||
|
||||
from common import connection_argument_parser, SessionCache
|
||||
from gqlalchemy import wait_for_port
|
||||
from multiprocessing import Pool, Manager
|
||||
|
||||
# Constants and args
|
||||
@@ -83,18 +84,11 @@ def clean_memgraph():
|
||||
proc_mg.wait()
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", port]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def run_memgraph():
|
||||
global proc_mg
|
||||
proc_mg = subprocess.Popen(cmd, cwd=cwd)
|
||||
# Wait for Memgraph to finish the recovery process
|
||||
wait_for_server(args.endpoint.split(":")[1])
|
||||
wait_for_port(port=args.endpoint.split(":")[1], delay=0.1)
|
||||
|
||||
|
||||
def run_client(id, data):
|
||||
|
||||
@@ -2213,8 +2213,6 @@ TEST_P(CypherMainVisitorTest, GrantPrivilege) {
|
||||
{AuthQuery::Privilege::MODULE_READ});
|
||||
check_auth_query(&ast_generator, "GRANT MODULE_WRITE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
|
||||
{AuthQuery::Privilege::MODULE_WRITE});
|
||||
check_auth_query(&ast_generator, "GRANT SCHEMA TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
|
||||
{AuthQuery::Privilege::SCHEMA});
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, DenyPrivilege) {
|
||||
@@ -2255,8 +2253,6 @@ TEST_P(CypherMainVisitorTest, DenyPrivilege) {
|
||||
{AuthQuery::Privilege::MODULE_READ});
|
||||
check_auth_query(&ast_generator, "DENY MODULE_WRITE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
|
||||
{AuthQuery::Privilege::MODULE_WRITE});
|
||||
check_auth_query(&ast_generator, "DENY SCHEMA TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
|
||||
{AuthQuery::Privilege::SCHEMA});
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, RevokePrivilege) {
|
||||
@@ -2299,8 +2295,6 @@ TEST_P(CypherMainVisitorTest, RevokePrivilege) {
|
||||
{}, {AuthQuery::Privilege::MODULE_READ});
|
||||
check_auth_query(&ast_generator, "REVOKE MODULE_WRITE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
|
||||
{}, {AuthQuery::Privilege::MODULE_WRITE});
|
||||
check_auth_query(&ast_generator, "REVOKE SCHEMA FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
|
||||
{AuthQuery::Privilege::SCHEMA});
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, ShowPrivileges) {
|
||||
@@ -4217,76 +4211,3 @@ TEST_P(CypherMainVisitorTest, Foreach) {
|
||||
ASSERT_TRUE(dynamic_cast<RemoveProperty *>(*++clauses.begin()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, TestShowSchemas) {
|
||||
auto &ast_generator = *GetParam();
|
||||
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMAS"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMAS);
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, TestShowSchema) {
|
||||
auto &ast_generator = *GetParam();
|
||||
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA ON label"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA :label"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA label"), SyntaxException);
|
||||
|
||||
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMA ON :label"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label");
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, TestCreateSchema) {
|
||||
auto &ast_generator = *GetParam();
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label()"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(123 INTEGER)"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name TYPE)"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, age)"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, DURATION)"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON label(name INTEGER)"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name INTEGER)"), SemanticException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name STRING)"), SemanticException);
|
||||
|
||||
{
|
||||
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label1(name STRING)"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label1");
|
||||
}
|
||||
{
|
||||
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label2(name string)"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label2");
|
||||
}
|
||||
{
|
||||
auto *query = dynamic_cast<SchemaQuery *>(
|
||||
ast_generator.ParseQuery("CREATE SCHEMA ON :label3(first_name STRING, last_name STRING)"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label3");
|
||||
}
|
||||
{
|
||||
auto *query = dynamic_cast<SchemaQuery *>(
|
||||
ast_generator.ParseQuery("CREATE SCHEMA ON :label4(name STRING, age INTEGER, dur DURATION, birthday "
|
||||
"LOCALDATETIME, some_time LOCALTIME, speaks_truth BOOL)"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label4");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, TestDropSchema) {
|
||||
auto &ast_generator = *GetParam();
|
||||
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON label"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA :label"), SyntaxException);
|
||||
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON :label()"), SyntaxException);
|
||||
|
||||
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("DROP SCHEMA ON :label"));
|
||||
ASSERT_TRUE(query);
|
||||
EXPECT_EQ(query->action_, SchemaQuery::Action::DROP_SCHEMA);
|
||||
EXPECT_EQ(query->label_.name, "label");
|
||||
}
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "communication/result_stream_faker.hpp"
|
||||
@@ -40,12 +38,6 @@ auto ToEdgeList(const memgraph::communication::bolt::Value &v) {
|
||||
list.push_back(x.ValueEdge());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
auto StringToUnorderedSet(const std::string &element, const size_t number_of_split_elements) {
|
||||
const auto element_split = memgraph::utils::Split(element, ", ");
|
||||
MG_ASSERT(element_split.size() == number_of_split_elements);
|
||||
return std::unordered_set<std::string>(element_split.begin(), element_split.end());
|
||||
};
|
||||
|
||||
struct InterpreterFaker {
|
||||
@@ -1473,148 +1465,3 @@ TEST_F(InterpreterTest, LoadCsvClauseNotification) {
|
||||
"conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
|
||||
ASSERT_EQ(notification["description"].ValueString(), "");
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, CreateSchemaMulticommandTransaction) {
|
||||
Interpret("BEGIN");
|
||||
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"),
|
||||
memgraph::query::ConstraintInMulticommandTxException);
|
||||
Interpret("ROLLBACK");
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, ShowSchemasMulticommandTransaction) {
|
||||
Interpret("BEGIN");
|
||||
ASSERT_THROW(Interpret("SHOW SCHEMAS"), memgraph::query::ConstraintInMulticommandTxException);
|
||||
Interpret("ROLLBACK");
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) {
|
||||
Interpret("BEGIN");
|
||||
ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
|
||||
Interpret("ROLLBACK");
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) {
|
||||
Interpret("BEGIN");
|
||||
ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
|
||||
Interpret("ROLLBACK");
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, SchemaTestCreateAndShow) {
|
||||
// Empty schema type map should result with syntax exception.
|
||||
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::SyntaxException);
|
||||
|
||||
// Duplicate properties are should also cause an exception
|
||||
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"), memgraph::query::SemanticException);
|
||||
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name INTEGER);"), memgraph::query::SemanticException);
|
||||
|
||||
{
|
||||
// Cannot create same schema twice
|
||||
Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
|
||||
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::QueryException);
|
||||
}
|
||||
// Show schema
|
||||
{
|
||||
auto stream = Interpret("SHOW SCHEMA ON :label");
|
||||
ASSERT_EQ(stream.GetHeader().size(), 2U);
|
||||
const auto &header = stream.GetHeader();
|
||||
ASSERT_EQ(header[0], "property_name");
|
||||
ASSERT_EQ(header[1], "property_type");
|
||||
ASSERT_EQ(stream.GetResults().size(), 2U);
|
||||
std::unordered_map<std::string, std::string> result_table{{"age", "Integer"}, {"name", "String"}};
|
||||
|
||||
const auto &result = stream.GetResults().front();
|
||||
ASSERT_EQ(result.size(), 2U);
|
||||
const auto key1 = result[0].ValueString();
|
||||
ASSERT_TRUE(result_table.contains(key1));
|
||||
ASSERT_EQ(result[1].ValueString(), result_table[key1]);
|
||||
|
||||
const auto &result2 = stream.GetResults().front();
|
||||
ASSERT_EQ(result2.size(), 2U);
|
||||
const auto key2 = result2[0].ValueString();
|
||||
ASSERT_TRUE(result_table.contains(key2));
|
||||
ASSERT_EQ(result[1].ValueString(), result_table[key2]);
|
||||
}
|
||||
// Create Another Schema
|
||||
Interpret("CREATE SCHEMA ON :label2(place STRING, dur DURATION)");
|
||||
|
||||
// Show schemas
|
||||
{
|
||||
auto stream = Interpret("SHOW SCHEMAS");
|
||||
ASSERT_EQ(stream.GetHeader().size(), 3U);
|
||||
const auto &header = stream.GetHeader();
|
||||
ASSERT_EQ(header[0], "label");
|
||||
ASSERT_EQ(header[1], "primary_key");
|
||||
ASSERT_EQ(header[2], "primary_key_type");
|
||||
ASSERT_EQ(stream.GetResults().size(), 2U);
|
||||
std::unordered_map<std::string, std::pair<std::unordered_set<std::string>, std::string>> result_table{
|
||||
{"label", {{"name::String", "age::Integer"}, "Composite"}},
|
||||
{"label2", {{"place::String", "dur::Duration"}, "Composite"}}};
|
||||
|
||||
const auto &result = stream.GetResults().front();
|
||||
ASSERT_EQ(result.size(), 3U);
|
||||
const auto key1 = result[0].ValueString();
|
||||
ASSERT_TRUE(result_table.contains(key1));
|
||||
const auto primary_key_split = StringToUnorderedSet(result[1].ValueString(), 2);
|
||||
ASSERT_TRUE(primary_key_split == result_table[key1].first);
|
||||
ASSERT_EQ(result[2].ValueString(), result_table[key1].second);
|
||||
|
||||
const auto &result2 = stream.GetResults().front();
|
||||
ASSERT_EQ(result2.size(), 3U);
|
||||
const auto key2 = result2[0].ValueString();
|
||||
ASSERT_TRUE(result_table.contains(key2));
|
||||
const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString(), 2);
|
||||
ASSERT_TRUE(primary_key_split2 == result_table[key2].first);
|
||||
ASSERT_EQ(result2[2].ValueString(), result_table[key2].second);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) {
|
||||
Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
|
||||
// Wrong syntax for dropping schema.
|
||||
ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::SyntaxException);
|
||||
// Cannot drop non existant schema.
|
||||
ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::QueryException);
|
||||
|
||||
// Create Schema and Drop
|
||||
auto get_number_of_schemas = [this]() {
|
||||
auto stream = Interpret("SHOW SCHEMAS");
|
||||
return stream.GetResults().size();
|
||||
};
|
||||
|
||||
ASSERT_EQ(get_number_of_schemas(), 1);
|
||||
Interpret("CREATE SCHEMA ON :label1(name STRING, age INTEGER)");
|
||||
ASSERT_EQ(get_number_of_schemas(), 2);
|
||||
Interpret("CREATE SCHEMA ON :label2(name STRING, sex BOOL)");
|
||||
ASSERT_EQ(get_number_of_schemas(), 3);
|
||||
Interpret("DROP SCHEMA ON :label1");
|
||||
ASSERT_EQ(get_number_of_schemas(), 2);
|
||||
Interpret("CREATE SCHEMA ON :label3(name STRING, birthday LOCALDATETIME)");
|
||||
ASSERT_EQ(get_number_of_schemas(), 3);
|
||||
Interpret("DROP SCHEMA ON :label2");
|
||||
ASSERT_EQ(get_number_of_schemas(), 2);
|
||||
Interpret("CREATE SCHEMA ON :label4(name STRING, age DURATION)");
|
||||
ASSERT_EQ(get_number_of_schemas(), 3);
|
||||
Interpret("DROP SCHEMA ON :label3");
|
||||
ASSERT_EQ(get_number_of_schemas(), 2);
|
||||
Interpret("DROP SCHEMA ON :label");
|
||||
ASSERT_EQ(get_number_of_schemas(), 1);
|
||||
|
||||
// Show schemas
|
||||
auto stream = Interpret("SHOW SCHEMAS");
|
||||
ASSERT_EQ(stream.GetHeader().size(), 3U);
|
||||
const auto &header = stream.GetHeader();
|
||||
ASSERT_EQ(header[0], "label");
|
||||
ASSERT_EQ(header[1], "primary_key");
|
||||
ASSERT_EQ(header[2], "primary_key_type");
|
||||
ASSERT_EQ(stream.GetResults().size(), 1U);
|
||||
std::unordered_map<std::string, std::pair<std::unordered_set<std::string>, std::string>> result_table{
|
||||
{"label4", {{"name::String", "age::Duration"}, "Composite"}}};
|
||||
|
||||
const auto &result = stream.GetResults().front();
|
||||
ASSERT_EQ(result.size(), 3U);
|
||||
const auto key1 = result[0].ValueString();
|
||||
ASSERT_TRUE(result_table.contains(key1));
|
||||
const auto primary_key_split = StringToUnorderedSet(result[1].ValueString(), 2);
|
||||
ASSERT_TRUE(primary_key_split == result_table[key1].first);
|
||||
ASSERT_EQ(result[2].ValueString(), result_table[key1].second);
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ TEST(MgpTransTest, TestMgpTransApi) {
|
||||
// for different string cases as these are all handled by
|
||||
// IsValidIdentifier().
|
||||
// Maybe add a mock instead and expect IsValidIdentifier() to be called once?
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "dash-dash", no_op_cb), mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "dash-dash", no_op_cb), MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_TRUE(module.transformations.empty());
|
||||
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_NE(module.transformations.find("transform"), module.transformations.end());
|
||||
|
||||
// Try to register a transformation twice
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_transformation(&module, "transform", no_op_cb), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_TRUE(module.transformations.size() == 1);
|
||||
}
|
||||
|
||||
@@ -25,26 +25,25 @@ TEST(Module, InvalidFunctionRegistration) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
mgp_func *func{nullptr};
|
||||
// Other test cases are covered within the procedure API. This is only sanity check
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "dashes-not-supported", DummyCallback, &func),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "dashes-not-supported", DummyCallback, &func), MGP_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TEST(Module, RegisterSameFunctionMultipleTimes) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
mgp_func *func{nullptr};
|
||||
EXPECT_EQ(module.functions.find("same_name"), module.functions.end());
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_NE(module.functions.find("same_name"), module.functions.end());
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "same_name", DummyCallback, &func), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_NE(module.functions.find("same_name"), module.functions.end());
|
||||
}
|
||||
|
||||
TEST(Module, CaseSensitiveFunctionNames) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
mgp_func *func{nullptr};
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "not_same", DummyCallback, &func), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "NoT_saME", DummyCallback, &func), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "NOT_SAME", DummyCallback, &func), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "not_same", DummyCallback, &func), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "NoT_saME", DummyCallback, &func), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_function(&module, "NOT_SAME", DummyCallback, &func), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(module.functions.size(), 3U);
|
||||
}
|
||||
|
||||
@@ -25,34 +25,30 @@ TEST(Module, InvalidProcedureRegistration) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
mgp_proc *proc{nullptr};
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "dashes-not-supported", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
MGP_ERROR_INVALID_ARGUMENT);
|
||||
// as u8string this is u8"unicode\u22c6not\u2014supported"
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "unicode\xE2\x8B\x86not\xE2\x80\x94supported", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
MGP_ERROR_INVALID_ARGUMENT);
|
||||
// as u8string this is u8"`backticks⋆\u22c6won't-save\u2014you`"
|
||||
EXPECT_EQ(
|
||||
mgp_module_add_read_procedure(&module, "`backticks⋆\xE2\x8B\x86won't-save\xE2\x80\x94you`", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "42_name_must_not_start_with_number", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "div/", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "mul*", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "div/", DummyCallback, &proc), MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "mul*", DummyCallback, &proc), MGP_ERROR_INVALID_ARGUMENT);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "question_mark_is_not_valid?", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_INVALID_ARGUMENT);
|
||||
MGP_ERROR_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
TEST(Module, RegisteringTheSameProcedureMultipleTimes) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
mgp_proc *proc{nullptr};
|
||||
EXPECT_EQ(module.procedures.find("same_name"), module.procedures.end());
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_NE(module.procedures.find("same_name"), module.procedures.end());
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "same_name", DummyCallback, &proc), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_NE(module.procedures.find("same_name"), module.procedures.end());
|
||||
}
|
||||
|
||||
@@ -60,9 +56,9 @@ TEST(Module, CaseSensitiveProcedureNames) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
EXPECT_TRUE(module.procedures.empty());
|
||||
mgp_proc *proc{nullptr};
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "not_same", DummyCallback, &proc), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "NoT_saME", DummyCallback, &proc), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "NOT_SAME", DummyCallback, &proc), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "not_same", DummyCallback, &proc), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "NoT_saME", DummyCallback, &proc), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_module_add_read_procedure(&module, "NOT_SAME", DummyCallback, &proc), MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(module.procedures.size(), 3U);
|
||||
}
|
||||
|
||||
@@ -77,41 +73,37 @@ TEST(Module, ProcedureSignature) {
|
||||
mgp_module module(memgraph::utils::NewDeleteResource());
|
||||
auto *proc = EXPECT_MGP_NO_ERROR(mgp_proc *, mgp_module_add_read_procedure, &module, "proc", &DummyCallback);
|
||||
CheckSignature(proc, "proc() :: ()");
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)), MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc, "proc(arg1 :: NUMBER) :: ()");
|
||||
EXPECT_EQ(mgp_proc_add_opt_arg(
|
||||
proc, "opt1",
|
||||
EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: ()");
|
||||
EXPECT_EQ(
|
||||
mgp_proc_add_result(
|
||||
proc, "res1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_list, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int))),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_number)), MGP_ERROR_LOGIC_ERROR);
|
||||
CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_proc_add_arg(proc, "arg2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_map)), MGP_ERROR_LOGIC_ERROR);
|
||||
CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)");
|
||||
EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string)),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc,
|
||||
"proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: "
|
||||
"(res1 :: LIST OF INTEGER, DEPRECATED res2 :: STRING)");
|
||||
EXPECT_EQ(mgp_proc_add_result(proc, "res2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_proc_add_result(proc, "res2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)), MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(mgp_proc_add_deprecated_result(proc, "res1", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
|
||||
mgp_error::MGP_ERROR_LOGIC_ERROR);
|
||||
MGP_ERROR_LOGIC_ERROR);
|
||||
EXPECT_EQ(
|
||||
mgp_proc_add_opt_arg(proc, "opt2", EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_string),
|
||||
test_utils::CreateValueOwningPtr(
|
||||
EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_string, "string=\"value\"", &memory))
|
||||
.get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc,
|
||||
"proc(arg1 :: NUMBER, opt1 = Null :: ANY?, "
|
||||
"opt2 = \"string=\\\"value\\\"\" :: STRING) :: "
|
||||
@@ -126,7 +118,7 @@ TEST(Module, ProcedureSignatureOnlyOptArg) {
|
||||
proc, "opt1",
|
||||
EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_nullable, EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)),
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
CheckSignature(proc, "proc(opt1 = Null :: ANY?) :: ()");
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ TEST(CypherType, MapSatisfiesType) {
|
||||
mgp_map_insert(
|
||||
map, "key",
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
auto *mgp_map_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_map, map);
|
||||
const memgraph::query::TypedValue tv_map(
|
||||
std::map<std::string, memgraph::query::TypedValue>{{"key", memgraph::query::TypedValue(42)}});
|
||||
@@ -287,7 +287,7 @@ TEST(CypherType, PathSatisfiesType) {
|
||||
ASSERT_TRUE(path);
|
||||
alloc.delete_object(mgp_vertex_v);
|
||||
auto mgp_edge_v = alloc.new_object<mgp_edge>(edge, &graph);
|
||||
ASSERT_EQ(mgp_path_expand(path, mgp_edge_v), mgp_error::MGP_ERROR_NO_ERROR);
|
||||
ASSERT_EQ(mgp_path_expand(path, mgp_edge_v), MGP_ERROR_NO_ERROR);
|
||||
alloc.delete_object(mgp_edge_v);
|
||||
auto *mgp_path_v = EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_path, path);
|
||||
const memgraph::query::TypedValue tv_path(memgraph::query::Path(v1, edge, v2));
|
||||
@@ -343,7 +343,7 @@ TEST(CypherType, ListOfIntSatisfiesType) {
|
||||
mgp_list_append(
|
||||
list,
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, i, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
tv_list.ValueList().emplace_back(i);
|
||||
auto valid_types =
|
||||
MakeListTypes({EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any), EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_int),
|
||||
@@ -371,14 +371,14 @@ TEST(CypherType, ListOfIntAndBoolSatisfiesType) {
|
||||
mgp_list_append(
|
||||
list,
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_int, 42, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
tv_list.ValueList().emplace_back(42);
|
||||
// Add a boolean
|
||||
ASSERT_EQ(
|
||||
mgp_list_append(
|
||||
list,
|
||||
test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_bool, 1, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
tv_list.ValueList().emplace_back(true);
|
||||
auto valid_types = MakeListTypes({EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any)});
|
||||
valid_types.push_back(EXPECT_MGP_NO_ERROR(mgp_type *, mgp_type_any));
|
||||
@@ -402,7 +402,7 @@ TEST(CypherType, ListOfNullSatisfiesType) {
|
||||
ASSERT_EQ(
|
||||
mgp_list_append(
|
||||
list, test_utils::CreateValueOwningPtr(EXPECT_MGP_NO_ERROR(mgp_value *, mgp_value_make_null, &memory)).get()),
|
||||
mgp_error::MGP_ERROR_NO_ERROR);
|
||||
MGP_ERROR_NO_ERROR);
|
||||
tv_list.ValueList().emplace_back();
|
||||
// List with Null satisfies all nullable list element types
|
||||
std::vector<mgp_type *> primitive_types{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user