Compare commits
4 Commits
MG-fixed-s
...
use-expr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2faa58fe21 | ||
|
|
40b2e12eaf | ||
|
|
0e613a1ff9 | ||
|
|
4b5cbb99b6 |
224
.github/workflows/diff.yaml
vendored
224
.github/workflows/diff.yaml
vendored
@@ -164,14 +164,52 @@ jobs:
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run simulation tests
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run simulation tests.
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -R memgraph__simulation --output-on-failure -j$THREADS
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark|memgraph__simulation)" --output-on-failure
|
||||
|
||||
- name: Run drivers tests
|
||||
run: |
|
||||
./tests/drivers/run.sh
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd tests/integration
|
||||
for name in *; do
|
||||
if [ ! -d $name ]; then continue; fi
|
||||
pushd $name >/dev/null
|
||||
echo "Running: $name"
|
||||
if [ -x prepare.sh ]; then
|
||||
./prepare.sh
|
||||
fi
|
||||
if [ -x runner.py ]; then
|
||||
./runner.py
|
||||
elif [ -x runner.sh ]; then
|
||||
./runner.sh
|
||||
fi
|
||||
echo
|
||||
popd >/dev/null
|
||||
done
|
||||
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
@@ -202,6 +240,19 @@ jobs:
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
@@ -216,7 +267,7 @@ jobs:
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run simulation tests.
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__simulation --output-on-failure -j$THREADS
|
||||
|
||||
@@ -227,4 +278,167 @@ jobs:
|
||||
./setup.sh
|
||||
source ve3/bin/activate
|
||||
cd e2e
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory ./distributed_queries
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (SSL)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --use-ssl
|
||||
|
||||
- name: Run durability test
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create enterprise DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
#continue-on-error: true
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build only memgraph release binarie.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS memgraph
|
||||
|
||||
- name: Run Jepsen tests
|
||||
run: |
|
||||
cd tests/jepsen
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
path: tests/jepsen/Jepsen.tar.gz
|
||||
|
||||
release_benchmarks:
|
||||
name: "Release benchmarks"
|
||||
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build only memgraph release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run macro benchmarks
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Get branch name (merge)
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get branch name (pull request)
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload macro benchmark results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
virtualenv -p python3 ve3
|
||||
source ve3/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./main.py --benchmark-name "macro_benchmark" \
|
||||
--benchmark-results-path "../../tests/macro_benchmark/.harness_summary" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
- name: Run mgbench
|
||||
run: |
|
||||
cd tests/mgbench
|
||||
./benchmark.py --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
|
||||
|
||||
- name: Upload mgbench results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
virtualenv -p python3 ve3
|
||||
source ve3/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./main.py --benchmark-name "mgbench" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_result.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
@@ -83,9 +83,13 @@ modifications:
|
||||
value: "true"
|
||||
override: true
|
||||
|
||||
# - name: "query_modules_directory"
|
||||
# value: "/usr/lib/memgraph/query_modules"
|
||||
# override: true
|
||||
- name: "query_modules_directory"
|
||||
value: "/usr/lib/memgraph/query_modules"
|
||||
override: true
|
||||
|
||||
- name: "auth_module_executable"
|
||||
value: "/usr/lib/memgraph/auth_module/example.py"
|
||||
override: false
|
||||
|
||||
- name: "memory_limit"
|
||||
value: "0"
|
||||
|
||||
@@ -116,7 +116,7 @@ declare -A primary_urls=(
|
||||
["pymgclient"]="http://$local_cache_host/git/pymgclient.git"
|
||||
["mgconsole"]="http://$local_cache_host/git/mgconsole.git"
|
||||
["spdlog"]="http://$local_cache_host/git/spdlog"
|
||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/9d69186291aca4f0137b69c1dee313b391ff564c/single_include/nlohmann/json.hpp"
|
||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||
["neo4j"]="http://$local_cache_host/file/neo4j-community-3.2.3-unix.tar.gz"
|
||||
["librdkafka"]="http://$local_cache_host/git/librdkafka.git"
|
||||
["protobuf"]="http://$local_cache_host/git/protobuf.git"
|
||||
@@ -141,7 +141,7 @@ declare -A secondary_urls=(
|
||||
["pymgclient"]="https://github.com/memgraph/pymgclient.git"
|
||||
["mgconsole"]="http://github.com/memgraph/mgconsole.git"
|
||||
["spdlog"]="https://github.com/gabime/spdlog"
|
||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/9d69186291aca4f0137b69c1dee313b391ff564c/single_include/nlohmann/json.hpp"
|
||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||
["neo4j"]="https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/neo4j-community-3.2.3-unix.tar.gz"
|
||||
["librdkafka"]="https://github.com/edenhill/librdkafka.git"
|
||||
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
|
||||
|
||||
@@ -37,13 +37,13 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# Memgraph Single Node v2 Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
set(mg_single_node_v2_sources
|
||||
glue/v2/communication.cpp
|
||||
memgraph.cpp
|
||||
glue/v2/auth.cpp
|
||||
glue/communication.cpp
|
||||
memgraph.cpp
|
||||
glue/auth.cpp
|
||||
)
|
||||
|
||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||
telemetry_lib mg-query-v2 mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-io mg-coordinator)
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings)
|
||||
if (MG_ENTERPRISE)
|
||||
# These are enterprise subsystems
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||
@@ -126,3 +126,19 @@ install(CODE "file(MAKE_DIRECTORY \$ENV{DESTDIR}/var/log/memgraph
|
||||
# ----------------------------------------------------------------------------
|
||||
# Memgraph CSV Import Tool Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
add_executable(mg_import_csv mg_import_csv.cpp)
|
||||
target_link_libraries(mg_import_csv mg-storage-v2)
|
||||
|
||||
# Strip the executable in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET mg_import_csv POST_BUILD
|
||||
COMMAND strip -s mg_import_csv
|
||||
COMMENT "Stripping symbols and sections from mg_import_csv")
|
||||
endif()
|
||||
|
||||
install(TARGETS mg_import_csv RUNTIME DESTINATION bin)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Memgraph CSV Import Tool Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/v2/pool.hpp"
|
||||
#include "communication/v2/session.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
@@ -59,10 +58,10 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
|
||||
bool IsRunning() const noexcept { return alive_.load(std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
Listener(boost::asio::io_context &io_context, TSessionData &data, ServerContext *server_context,
|
||||
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),
|
||||
data_(data),
|
||||
server_context_(server_context),
|
||||
acceptor_(io_context_),
|
||||
endpoint_{endpoint},
|
||||
@@ -111,7 +110,7 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
|
||||
return OnError(ec, "accept");
|
||||
}
|
||||
|
||||
auto session = SessionHandler::Create(std::move(socket), *data_, *server_context_, endpoint_, inactivity_timeout_,
|
||||
auto session = SessionHandler::Create(std::move(socket), data_, *server_context_, endpoint_, inactivity_timeout_,
|
||||
service_name_);
|
||||
session->Start();
|
||||
DoAccept();
|
||||
|
||||
@@ -72,7 +72,7 @@ class Server final {
|
||||
* Constructs and binds server to endpoint, operates on session data and
|
||||
* invokes workers_count workers
|
||||
*/
|
||||
Server(ServerEndpoint &endpoint, TSessionData &session_data, ServerContext *server_context,
|
||||
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},
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#include <boost/beast/websocket/rfc6455.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include "communication/buffer.hpp"
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
@@ -140,7 +139,7 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
|
||||
|
||||
private:
|
||||
// Take ownership of the socket
|
||||
explicit WebsocketSession(tcp::socket &&socket, TSessionData &data, tcp::endpoint endpoint,
|
||||
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())},
|
||||
@@ -312,13 +311,13 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Session(tcp::socket &&socket, TSessionData &data, ServerContext &server_context, tcp::endpoint endpoint,
|
||||
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},
|
||||
data_{data},
|
||||
endpoint_{endpoint},
|
||||
remote_endpoint_{GetRemoteEndpoint()},
|
||||
service_name_{service_name},
|
||||
@@ -374,7 +373,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
||||
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_)
|
||||
WebsocketSession<TSession, TSessionData>::Create(std::move(sock), data_, endpoint_, service_name_)
|
||||
->DoAccept(parser.release());
|
||||
execution_active_ = false;
|
||||
return;
|
||||
@@ -466,7 +465,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
||||
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_.count());
|
||||
spdlog::info("Shutting down session after {} of inactivity", timeout_seconds_);
|
||||
DoShutdown();
|
||||
} else {
|
||||
// Put the actor back to sleep.
|
||||
|
||||
@@ -158,10 +158,10 @@ class Coordinator {
|
||||
|
||||
private:
|
||||
ShardMap shard_map_;
|
||||
uint64_t highest_allocated_timestamp_{0};
|
||||
uint64_t highest_allocated_timestamp_;
|
||||
|
||||
/// Query engines need to periodically request batches of unique edge IDs.
|
||||
uint64_t highest_allocated_edge_id_{0};
|
||||
uint64_t highest_allocated_edge_id_;
|
||||
|
||||
CoordinatorReadResponses HandleRead(GetShardMapRequest && /* get_shard_map_request */) {
|
||||
GetShardMapResponse res;
|
||||
|
||||
@@ -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 <chrono>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <variant>
|
||||
|
||||
#include "coordinator/coordinator.hpp"
|
||||
#include "coordinator/coordinator_rsm.hpp"
|
||||
#include "coordinator/shard_map.hpp"
|
||||
#include "io/address.hpp"
|
||||
#include "io/future.hpp"
|
||||
#include "io/messages.hpp"
|
||||
#include "io/rsm/raft.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
|
||||
namespace memgraph::coordinator::coordinator_worker {
|
||||
|
||||
/// Obligations:
|
||||
/// * ShutDown
|
||||
/// * Cron
|
||||
/// * RouteMessage
|
||||
|
||||
using coordinator::Coordinator;
|
||||
using coordinator::CoordinatorRsm;
|
||||
using io::Address;
|
||||
using io::RequestId;
|
||||
using io::Time;
|
||||
using io::messages::CoordinatorMessages;
|
||||
using msgs::ReadRequests;
|
||||
using msgs::ReadResponses;
|
||||
using msgs::WriteRequests;
|
||||
using msgs::WriteResponses;
|
||||
|
||||
struct ShutDown {};
|
||||
|
||||
struct Cron {};
|
||||
|
||||
struct RouteMessage {
|
||||
CoordinatorMessages message;
|
||||
RequestId request_id;
|
||||
Address to;
|
||||
Address from;
|
||||
};
|
||||
|
||||
using Message = std::variant<RouteMessage, Cron, ShutDown>;
|
||||
|
||||
struct QueueInner {
|
||||
std::mutex mu{};
|
||||
std::condition_variable cv;
|
||||
// TODO(tyler) handle simulator communication std::shared_ptr<std::atomic<int>> blocked;
|
||||
|
||||
// TODO(tyler) investigate using a priority queue that prioritizes messages in a way that
|
||||
// improves overall QoS. For example, maybe we want to schedule raft Append messages
|
||||
// ahead of Read messages or generally writes before reads for lowering the load on the
|
||||
// overall system faster etc... When we do this, we need to make sure to avoid
|
||||
// starvation by sometimes randomizing priorities, rather than following a strict
|
||||
// prioritization.
|
||||
std::deque<Message> queue;
|
||||
};
|
||||
|
||||
/// There are two reasons to implement our own Queue instead of using
|
||||
/// one off-the-shelf:
|
||||
/// 1. we will need to know in the simulator when all threads are waiting
|
||||
/// 2. we will want to implement our own priority queue within this for QoS
|
||||
class Queue {
|
||||
std::shared_ptr<QueueInner> inner_ = std::make_shared<QueueInner>();
|
||||
|
||||
public:
|
||||
void Push(Message &&message) {
|
||||
{
|
||||
MG_ASSERT(inner_.use_count() > 0);
|
||||
std::unique_lock<std::mutex> lock(inner_->mu);
|
||||
|
||||
inner_->queue.emplace_back(std::move(message));
|
||||
} // lock dropped before notifying condition variable
|
||||
|
||||
inner_->cv.notify_all();
|
||||
}
|
||||
|
||||
Message Pop() {
|
||||
MG_ASSERT(inner_.use_count() > 0);
|
||||
std::unique_lock<std::mutex> lock(inner_->mu);
|
||||
|
||||
while (inner_->queue.empty()) {
|
||||
inner_->cv.wait(lock);
|
||||
}
|
||||
|
||||
Message message = std::move(inner_->queue.front());
|
||||
inner_->queue.pop_front();
|
||||
|
||||
return message;
|
||||
}
|
||||
};
|
||||
|
||||
/// A CoordinatorWorker owns Raft<CoordinatorRsm> instances. receives messages from the MachineManager.
|
||||
template <typename IoImpl>
|
||||
class CoordinatorWorker {
|
||||
io::Io<IoImpl> io_;
|
||||
Queue queue_;
|
||||
CoordinatorRsm<IoImpl> coordinator_;
|
||||
|
||||
bool Process(ShutDown && /*shut_down*/) { return false; }
|
||||
|
||||
bool Process(Cron && /* cron */) {
|
||||
coordinator_.Cron();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Process(RouteMessage &&route_message) {
|
||||
coordinator_.Handle(std::move(route_message.message), route_message.request_id, route_message.from);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
CoordinatorWorker(io::Io<IoImpl> io, Queue queue, Coordinator coordinator)
|
||||
: io_(std::move(io)),
|
||||
queue_(std::move(queue)),
|
||||
coordinator_{std::move(io_.ForkLocal()), {}, std::move(coordinator)} {}
|
||||
|
||||
CoordinatorWorker(CoordinatorWorker &&) noexcept = default;
|
||||
CoordinatorWorker &operator=(CoordinatorWorker &&) noexcept = default;
|
||||
CoordinatorWorker(const CoordinatorWorker &) = delete;
|
||||
CoordinatorWorker &operator=(const CoordinatorWorker &) = delete;
|
||||
~CoordinatorWorker() = default;
|
||||
|
||||
void Run() {
|
||||
while (true) {
|
||||
Message message = queue_.Pop();
|
||||
|
||||
const bool should_continue = std::visit(
|
||||
[this](auto &&msg) { return this->Process(std::forward<decltype(msg)>(msg)); }, std::move(message));
|
||||
|
||||
if (!should_continue) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator::coordinator_worker
|
||||
@@ -11,10 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <compare>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
||||
#include "io/time.hpp"
|
||||
|
||||
@@ -24,8 +21,8 @@ using Time = memgraph::io::Time;
|
||||
|
||||
/// Hybrid-logical clock
|
||||
struct Hlc {
|
||||
uint64_t logical_id = 0;
|
||||
Time coordinator_wall_clock = Time::min();
|
||||
uint64_t logical_id;
|
||||
Time coordinator_wall_clock;
|
||||
|
||||
auto operator<=>(const Hlc &other) const { return logical_id <=> other.logical_id; }
|
||||
|
||||
@@ -34,15 +31,6 @@ struct Hlc {
|
||||
bool operator==(const uint64_t other) const { return logical_id == other; }
|
||||
bool operator<(const uint64_t other) const { return logical_id < other; }
|
||||
bool operator>=(const uint64_t other) const { return logical_id >= other; }
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const Hlc &hlc) {
|
||||
auto wall_clock = std::chrono::system_clock::to_time_t(hlc.coordinator_wall_clock);
|
||||
in << "Hlc { logical_id: " << hlc.logical_id;
|
||||
in << ", coordinator_wall_clock: " << std::put_time(std::localtime(&wall_clock), "%F %T");
|
||||
in << " }";
|
||||
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -9,18 +9,8 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "coordinator/shard_map.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
@@ -67,288 +57,6 @@ PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ShardMap ShardMap::Parse(std::istream &input_stream) {
|
||||
ShardMap shard_map;
|
||||
const auto read_size = [&input_stream] {
|
||||
size_t size{0};
|
||||
input_stream >> size;
|
||||
return size;
|
||||
};
|
||||
|
||||
// Reads a string until the next whitespace
|
||||
const auto read_word = [&input_stream] {
|
||||
std::string word;
|
||||
input_stream >> word;
|
||||
return word;
|
||||
};
|
||||
|
||||
const auto read_names = [&read_size, &read_word] {
|
||||
const auto number_of_names = read_size();
|
||||
spdlog::trace("Reading {} names", number_of_names);
|
||||
std::vector<std::string> names;
|
||||
names.reserve(number_of_names);
|
||||
|
||||
for (auto name_index = 0; name_index < number_of_names; ++name_index) {
|
||||
names.push_back(read_word());
|
||||
spdlog::trace("Read '{}'", names.back());
|
||||
}
|
||||
return names;
|
||||
};
|
||||
|
||||
const auto read_line = [&input_stream] {
|
||||
std::string line;
|
||||
std::getline(input_stream, line);
|
||||
return line;
|
||||
};
|
||||
|
||||
const auto parse_type = [](const std::string &type) {
|
||||
static const auto type_map = std::unordered_map<std::string, common::SchemaType>{
|
||||
{"string", common::SchemaType::STRING}, {"int", common::SchemaType::INT}, {"bool", common::SchemaType::BOOL}};
|
||||
const auto lower_case_type = utils::ToLowerCase(type);
|
||||
auto it = type_map.find(lower_case_type);
|
||||
MG_ASSERT(it != type_map.end(), "Invalid type in split files: {}", type);
|
||||
return it->second;
|
||||
};
|
||||
|
||||
const auto parse_property_value = [](std::string text, const common::SchemaType type) {
|
||||
if (type == common::SchemaType::STRING) {
|
||||
return storage::v3::PropertyValue{std::move(text)};
|
||||
}
|
||||
if (type == common::SchemaType::INT) {
|
||||
size_t processed{0};
|
||||
int64_t value = std::stoll(text, &processed);
|
||||
MG_ASSERT(processed == text.size() || text[processed] == ' ', "Invalid integer format: '{}'", text);
|
||||
return storage::v3::PropertyValue{value};
|
||||
}
|
||||
LOG_FATAL("Not supported type: {}", utils::UnderlyingCast(type));
|
||||
};
|
||||
|
||||
spdlog::debug("Reading properties");
|
||||
const auto properties = read_names();
|
||||
MG_ASSERT(shard_map.AllocatePropertyIds(properties).size() == properties.size(),
|
||||
"Unexpected number of properties created!");
|
||||
|
||||
spdlog::debug("Reading edge types");
|
||||
const auto edge_types = read_names();
|
||||
MG_ASSERT(shard_map.AllocateEdgeTypeIds(edge_types).size() == edge_types.size(),
|
||||
"Unexpected number of properties created!");
|
||||
|
||||
spdlog::debug("Reading primary labels");
|
||||
const auto number_of_primary_labels = read_size();
|
||||
spdlog::debug("Reading {} primary labels", number_of_primary_labels);
|
||||
|
||||
for (auto label_index = 0; label_index < number_of_primary_labels; ++label_index) {
|
||||
const auto primary_label = read_word();
|
||||
spdlog::debug("Reading primary label named '{}'", primary_label);
|
||||
const auto number_of_primary_properties = read_size();
|
||||
spdlog::debug("Reading {} primary properties", number_of_primary_properties);
|
||||
std::vector<std::string> pp_names;
|
||||
std::vector<common::SchemaType> pp_types;
|
||||
pp_names.reserve(number_of_primary_properties);
|
||||
pp_types.reserve(number_of_primary_properties);
|
||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
||||
pp_names.push_back(read_word());
|
||||
spdlog::debug("Reading primary property named '{}'", pp_names.back());
|
||||
pp_types.push_back(parse_type(read_word()));
|
||||
}
|
||||
auto pp_mapping = shard_map.AllocatePropertyIds(pp_names);
|
||||
std::vector<SchemaProperty> schema;
|
||||
schema.reserve(number_of_primary_properties);
|
||||
|
||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
||||
schema.push_back(storage::v3::SchemaProperty{pp_mapping.at(pp_names[property_index]), pp_types[property_index]});
|
||||
}
|
||||
const auto hlc = shard_map.GetHlc();
|
||||
MG_ASSERT(shard_map.InitializeNewLabel(primary_label, schema, 1, hlc).has_value(),
|
||||
"Cannot initialize new label: {}", primary_label);
|
||||
|
||||
const auto number_of_split_points = read_size();
|
||||
spdlog::debug("Reading {} split points", number_of_split_points);
|
||||
|
||||
[[maybe_unused]] const auto remainder_from_last_line = read_line();
|
||||
for (auto split_point_index = 0; split_point_index < number_of_split_points; ++split_point_index) {
|
||||
const auto line = read_line();
|
||||
spdlog::debug("Read split point '{}'", line);
|
||||
MG_ASSERT(line.front() == '[', "Invalid split file format!");
|
||||
MG_ASSERT(line.back() == ']', "Invalid split file format!");
|
||||
std::string_view line_view{line};
|
||||
line_view.remove_prefix(1);
|
||||
line_view.remove_suffix(1);
|
||||
static constexpr std::string_view kDelimiter{","};
|
||||
auto pk_values_as_text = utils::Split(line_view, kDelimiter);
|
||||
std::vector<PropertyValue> pk;
|
||||
pk.reserve(number_of_primary_properties);
|
||||
MG_ASSERT(pk_values_as_text.size() == number_of_primary_properties,
|
||||
"Split point contains invalid number of values '{}'", line);
|
||||
|
||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
||||
pk.push_back(parse_property_value(std::move(pk_values_as_text[property_index]), schema[property_index].type));
|
||||
}
|
||||
shard_map.SplitShard(shard_map.GetHlc(), shard_map.labels.at(primary_label), pk);
|
||||
}
|
||||
}
|
||||
|
||||
return shard_map;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &in, const ShardMap &shard_map) {
|
||||
using utils::print_helpers::operator<<;
|
||||
|
||||
in << "ShardMap { shard_map_version: " << shard_map.shard_map_version;
|
||||
in << ", max_property_id: " << shard_map.max_property_id;
|
||||
in << ", max_edge_type_id: " << shard_map.max_edge_type_id;
|
||||
in << ", properties: " << shard_map.properties;
|
||||
in << ", edge_types: " << shard_map.edge_types;
|
||||
in << ", max_label_id: " << shard_map.max_label_id;
|
||||
in << ", labels: " << shard_map.labels;
|
||||
in << ", label_spaces: " << shard_map.label_spaces;
|
||||
in << ", schemas: " << shard_map.schemas;
|
||||
in << "}";
|
||||
return in;
|
||||
}
|
||||
|
||||
Shards ShardMap::GetShardsForLabel(const LabelName &label) const {
|
||||
const auto id = labels.at(label);
|
||||
const auto &shards = label_spaces.at(id).shards;
|
||||
return shards;
|
||||
}
|
||||
|
||||
std::vector<Shards> ShardMap::GetAllShards() const {
|
||||
std::vector<Shards> all_shards;
|
||||
all_shards.reserve(label_spaces.size());
|
||||
std::transform(label_spaces.begin(), label_spaces.end(), std::back_inserter(all_shards),
|
||||
[](const auto &label_space) { return label_space.second.shards; });
|
||||
return all_shards;
|
||||
}
|
||||
|
||||
// TODO(gabor) later we will want to update the wallclock time with
|
||||
// the given Io<impl>'s time as well
|
||||
Hlc ShardMap::IncrementShardMapVersion() noexcept {
|
||||
++shard_map_version.logical_id;
|
||||
return shard_map_version;
|
||||
}
|
||||
|
||||
// TODO(antaljanosbenjamin) use a single map for all name id
|
||||
// mapping and a single counter to maintain the next id
|
||||
std::unordered_map<uint64_t, std::string> ShardMap::IdToNames() {
|
||||
std::unordered_map<uint64_t, std::string> id_to_names;
|
||||
|
||||
const auto map_type_ids = [&id_to_names](const auto &name_to_id_type) {
|
||||
for (const auto &[name, id] : name_to_id_type) {
|
||||
id_to_names.emplace(id.AsUint(), name);
|
||||
}
|
||||
};
|
||||
|
||||
map_type_ids(edge_types);
|
||||
map_type_ids(labels);
|
||||
map_type_ids(properties);
|
||||
|
||||
return id_to_names;
|
||||
}
|
||||
|
||||
Hlc ShardMap::GetHlc() const noexcept { return shard_map_version; }
|
||||
|
||||
std::vector<ShardToInitialize> ShardMap::AssignShards(Address storage_manager,
|
||||
std::set<boost::uuids::uuid> initialized) {
|
||||
std::vector<ShardToInitialize> ret{};
|
||||
|
||||
bool mutated = false;
|
||||
|
||||
for (auto &[label_id, label_space] : label_spaces) {
|
||||
for (auto it = label_space.shards.begin(); it != label_space.shards.end(); it++) {
|
||||
auto &[low_key, shard] = *it;
|
||||
std::optional<PrimaryKey> high_key;
|
||||
if (const auto next_it = std::next(it); next_it != label_space.shards.end()) {
|
||||
high_key = next_it->first;
|
||||
}
|
||||
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
|
||||
bool machine_contains_shard = false;
|
||||
|
||||
for (auto &aas : shard) {
|
||||
if (initialized.contains(aas.address.unique_id)) {
|
||||
machine_contains_shard = true;
|
||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
||||
spdlog::info("marking shard as full consensus participant: {}", aas.address.unique_id);
|
||||
aas.status = Status::CONSENSUS_PARTICIPANT;
|
||||
}
|
||||
} else {
|
||||
const bool same_machine = aas.address.last_known_ip == storage_manager.last_known_ip &&
|
||||
aas.address.last_known_port == storage_manager.last_known_port;
|
||||
if (same_machine) {
|
||||
machine_contains_shard = true;
|
||||
spdlog::info("reminding shard manager that they should begin participating in shard");
|
||||
|
||||
ret.push_back(ShardToInitialize{
|
||||
.uuid = aas.address.unique_id,
|
||||
.label_id = label_id,
|
||||
.min_key = low_key,
|
||||
.max_key = high_key,
|
||||
.schema = schemas[label_id],
|
||||
.config = Config{},
|
||||
.id_to_names = IdToNames(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!machine_contains_shard && shard.size() < label_space.replication_factor) {
|
||||
Address address = storage_manager;
|
||||
|
||||
// TODO(tyler) use deterministic UUID so that coordinators don't diverge here
|
||||
address.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
|
||||
|
||||
spdlog::info("assigning shard manager to shard");
|
||||
|
||||
ret.push_back(ShardToInitialize{
|
||||
.uuid = address.unique_id,
|
||||
.label_id = label_id,
|
||||
.min_key = low_key,
|
||||
.max_key = high_key,
|
||||
.schema = schemas[label_id],
|
||||
.config = Config{},
|
||||
.id_to_names = IdToNames(),
|
||||
});
|
||||
|
||||
AddressAndStatus aas = {
|
||||
.address = address,
|
||||
.status = Status::INITIALIZING,
|
||||
};
|
||||
|
||||
shard.emplace_back(aas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ShardMap::SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
|
||||
if (previous_shard_map_version != shard_map_version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &label_space = label_spaces.at(label_id);
|
||||
auto &shards_in_map = label_space.shards;
|
||||
|
||||
MG_ASSERT(!shards_in_map.empty());
|
||||
MG_ASSERT(!shards_in_map.contains(key));
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
// Finding the Shard that the new PrimaryKey should map to.
|
||||
auto prev = std::prev(shards_in_map.upper_bound(key));
|
||||
Shard duplicated_shard = prev->second;
|
||||
|
||||
// Apply the split
|
||||
shards_in_map[key] = duplicated_shard;
|
||||
|
||||
IncrementShardMapVersion();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
|
||||
size_t replication_factor, Hlc last_shard_map_version) {
|
||||
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
|
||||
@@ -367,11 +75,10 @@ std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std:
|
||||
};
|
||||
|
||||
LabelSpace label_space{
|
||||
.schema = schema,
|
||||
.schema = std::move(schema),
|
||||
.shards = shards,
|
||||
.replication_factor = replication_factor,
|
||||
};
|
||||
schemas[label_id] = std::move(schema);
|
||||
|
||||
label_spaces.emplace(label_id, label_space);
|
||||
|
||||
@@ -380,173 +87,4 @@ std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std:
|
||||
return label_id;
|
||||
}
|
||||
|
||||
void ShardMap::AddServer(Address server_address) {
|
||||
// Find a random place for the server to plug in
|
||||
}
|
||||
std::optional<LabelId> ShardMap::GetLabelId(const std::string &label) const {
|
||||
if (const auto it = labels.find(label); it != labels.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string &ShardMap::GetLabelName(const LabelId label) const {
|
||||
if (const auto it =
|
||||
std::ranges::find_if(labels, [label](const auto &name_id_pair) { return name_id_pair.second == label; });
|
||||
it != labels.end()) {
|
||||
return it->first;
|
||||
}
|
||||
throw utils::BasicException("GetLabelName fails on the given label id!");
|
||||
}
|
||||
|
||||
std::optional<PropertyId> ShardMap::GetPropertyId(const std::string &property_name) const {
|
||||
if (const auto it = properties.find(property_name); it != properties.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string &ShardMap::GetPropertyName(const PropertyId property) const {
|
||||
if (const auto it = std::ranges::find_if(
|
||||
properties, [property](const auto &name_id_pair) { return name_id_pair.second == property; });
|
||||
it != properties.end()) {
|
||||
return it->first;
|
||||
}
|
||||
throw utils::BasicException("PropertyId not found!");
|
||||
}
|
||||
|
||||
std::optional<EdgeTypeId> ShardMap::GetEdgeTypeId(const std::string &edge_type) const {
|
||||
if (const auto it = edge_types.find(edge_type); it != edge_types.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::string &ShardMap::GetEdgeTypeName(const EdgeTypeId property) const {
|
||||
if (const auto it = std::ranges::find_if(
|
||||
edge_types, [property](const auto &name_id_pair) { return name_id_pair.second == property; });
|
||||
it != edge_types.end()) {
|
||||
return it->first;
|
||||
}
|
||||
throw utils::BasicException("EdgeTypeId not found!");
|
||||
}
|
||||
|
||||
Shards ShardMap::GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key,
|
||||
const PrimaryKey &end_key) const {
|
||||
MG_ASSERT(start_key <= end_key);
|
||||
MG_ASSERT(labels.contains(label_name));
|
||||
|
||||
LabelId label_id = labels.at(label_name);
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
const auto &shards_for_label = label_space.shards;
|
||||
|
||||
MG_ASSERT(shards_for_label.begin()->first <= start_key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
auto it = std::prev(shards_for_label.upper_bound(start_key));
|
||||
const auto end_it = shards_for_label.upper_bound(end_key);
|
||||
|
||||
Shards shards{};
|
||||
|
||||
std::copy(it, end_it, std::inserter(shards, shards.end()));
|
||||
|
||||
return shards;
|
||||
}
|
||||
|
||||
const Shard &ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
|
||||
MG_ASSERT(labels.contains(label_name));
|
||||
|
||||
LabelId label_id = labels.at(label_name);
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
||||
}
|
||||
|
||||
const Shard &ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
||||
}
|
||||
|
||||
PropertyMap ShardMap::AllocatePropertyIds(const std::vector<PropertyName> &new_properties) {
|
||||
PropertyMap ret{};
|
||||
|
||||
bool mutated = false;
|
||||
|
||||
for (const auto &property_name : new_properties) {
|
||||
if (properties.contains(property_name)) {
|
||||
auto property_id = properties.at(property_name);
|
||||
ret.emplace(property_name, property_id);
|
||||
} else {
|
||||
mutated = true;
|
||||
|
||||
const PropertyId property_id = PropertyId::FromUint(++max_property_id);
|
||||
ret.emplace(property_name, property_id);
|
||||
properties.emplace(property_name, property_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
EdgeTypeIdMap ShardMap::AllocateEdgeTypeIds(const std::vector<EdgeTypeName> &new_edge_types) {
|
||||
EdgeTypeIdMap ret;
|
||||
|
||||
bool mutated = false;
|
||||
|
||||
for (const auto &edge_type_name : new_edge_types) {
|
||||
if (edge_types.contains(edge_type_name)) {
|
||||
auto edge_type_id = edge_types.at(edge_type_name);
|
||||
ret.emplace(edge_type_name, edge_type_id);
|
||||
} else {
|
||||
mutated = true;
|
||||
|
||||
const EdgeTypeId edge_type_id = EdgeTypeId::FromUint(++max_edge_type_id);
|
||||
ret.emplace(edge_type_name, edge_type_id);
|
||||
edge_types.emplace(edge_type_name, edge_type_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ShardMap::ClusterInitialized() const {
|
||||
for (const auto &[label_id, label_space] : label_spaces) {
|
||||
for (const auto &[low_key, shard] : label_space.shards) {
|
||||
if (shard.size() < label_space.replication_factor) {
|
||||
spdlog::info("label_space below desired replication factor");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &aas : shard) {
|
||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
||||
spdlog::info("shard member not yet a CONSENSUS_PARTICIPANT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -25,20 +24,14 @@
|
||||
#include "io/address.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/print_helpers.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
constexpr int64_t kNotExistingId{0};
|
||||
|
||||
using memgraph::io::Address;
|
||||
using memgraph::storage::v3::Config;
|
||||
using memgraph::storage::v3::EdgeTypeId;
|
||||
using memgraph::storage::v3::LabelId;
|
||||
using memgraph::storage::v3::PropertyId;
|
||||
using memgraph::storage::v3::PropertyValue;
|
||||
@@ -55,24 +48,7 @@ enum class Status : uint8_t {
|
||||
struct AddressAndStatus {
|
||||
memgraph::io::Address address;
|
||||
Status status;
|
||||
|
||||
friend bool operator<(const AddressAndStatus &lhs, const AddressAndStatus &rhs) { return lhs.address < rhs.address; }
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const AddressAndStatus &address_and_status) {
|
||||
in << "AddressAndStatus { address: ";
|
||||
in << address_and_status.address;
|
||||
if (address_and_status.status == Status::CONSENSUS_PARTICIPANT) {
|
||||
in << ", status: CONSENSUS_PARTICIPANT }";
|
||||
} else {
|
||||
in << ", status: INITIALIZING }";
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
friend bool operator==(const AddressAndStatus &lhs, const AddressAndStatus &rhs) {
|
||||
return lhs.address == rhs.address;
|
||||
}
|
||||
};
|
||||
|
||||
using PrimaryKey = std::vector<PropertyValue>;
|
||||
@@ -80,98 +56,221 @@ using Shard = std::vector<AddressAndStatus>;
|
||||
using Shards = std::map<PrimaryKey, Shard>;
|
||||
using LabelName = std::string;
|
||||
using PropertyName = std::string;
|
||||
using EdgeTypeName = std::string;
|
||||
using PropertyMap = std::map<PropertyName, PropertyId>;
|
||||
using EdgeTypeIdMap = std::map<EdgeTypeName, EdgeTypeId>;
|
||||
|
||||
struct ShardToInitialize {
|
||||
boost::uuids::uuid uuid;
|
||||
LabelId label_id;
|
||||
PrimaryKey min_key;
|
||||
std::optional<PrimaryKey> max_key;
|
||||
std::vector<SchemaProperty> schema;
|
||||
Config config;
|
||||
std::unordered_map<uint64_t, std::string> id_to_names;
|
||||
};
|
||||
|
||||
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
|
||||
|
||||
struct LabelSpace {
|
||||
std::vector<SchemaProperty> schema;
|
||||
// Maps between the smallest primary key stored in the shard and the shard
|
||||
std::map<PrimaryKey, Shard> shards;
|
||||
size_t replication_factor;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const LabelSpace &label_space) {
|
||||
using utils::print_helpers::operator<<;
|
||||
|
||||
in << "LabelSpace { schema: ";
|
||||
in << label_space.schema;
|
||||
in << ", shards: ";
|
||||
in << label_space.shards;
|
||||
in << ", replication_factor: " << label_space.replication_factor << "}";
|
||||
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
struct ShardMap {
|
||||
Hlc shard_map_version;
|
||||
uint64_t max_property_id{kNotExistingId};
|
||||
uint64_t max_edge_type_id{kNotExistingId};
|
||||
uint64_t max_property_id;
|
||||
std::map<PropertyName, PropertyId> properties;
|
||||
std::map<EdgeTypeName, EdgeTypeId> edge_types;
|
||||
uint64_t max_label_id{kNotExistingId};
|
||||
uint64_t max_label_id;
|
||||
std::map<LabelName, LabelId> labels;
|
||||
std::map<LabelId, LabelSpace> label_spaces;
|
||||
std::map<LabelId, std::vector<SchemaProperty>> schemas;
|
||||
std::map<LabelName, LabelId> labels;
|
||||
|
||||
[[nodiscard]] static ShardMap Parse(std::istream &input_stream);
|
||||
friend std::ostream &operator<<(std::ostream &in, const ShardMap &shard_map);
|
||||
|
||||
Shards GetShardsForLabel(const LabelName &label) const;
|
||||
|
||||
std::vector<Shards> GetAllShards() const;
|
||||
Shards GetShards(const LabelName &label) {
|
||||
const auto id = labels.at(label);
|
||||
auto &shards = label_spaces.at(id).shards;
|
||||
return shards;
|
||||
}
|
||||
|
||||
// TODO(gabor) later we will want to update the wallclock time with
|
||||
// the given Io<impl>'s time as well
|
||||
Hlc IncrementShardMapVersion() noexcept;
|
||||
Hlc GetHlc() const noexcept;
|
||||
Hlc IncrementShardMapVersion() noexcept {
|
||||
++shard_map_version.logical_id;
|
||||
return shard_map_version;
|
||||
}
|
||||
|
||||
std::unordered_map<uint64_t, std::string> IdToNames();
|
||||
Hlc GetHlc() const noexcept { return shard_map_version; }
|
||||
|
||||
// Returns the shard UUIDs that have been assigned but not yet acknowledged for this storage manager
|
||||
std::vector<ShardToInitialize> AssignShards(Address storage_manager, std::set<boost::uuids::uuid> initialized);
|
||||
std::vector<ShardToInitialize> AssignShards(Address storage_manager, std::set<boost::uuids::uuid> initialized) {
|
||||
std::vector<ShardToInitialize> ret{};
|
||||
|
||||
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key);
|
||||
bool mutated = false;
|
||||
|
||||
for (auto &[label_id, label_space] : label_spaces) {
|
||||
for (auto &[low_key, shard] : label_space.shards) {
|
||||
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
|
||||
bool machine_contains_shard = false;
|
||||
|
||||
for (auto &aas : shard) {
|
||||
if (initialized.contains(aas.address.unique_id)) {
|
||||
spdlog::info("marking shard as full consensus participant: {}", aas.address.unique_id);
|
||||
aas.status = Status::CONSENSUS_PARTICIPANT;
|
||||
machine_contains_shard = true;
|
||||
} else {
|
||||
const bool same_machine = aas.address.last_known_ip == storage_manager.last_known_ip &&
|
||||
aas.address.last_known_port == storage_manager.last_known_port;
|
||||
if (same_machine) {
|
||||
machine_contains_shard = true;
|
||||
ret.push_back(ShardToInitialize{
|
||||
.uuid = aas.address.unique_id,
|
||||
.label_id = label_id,
|
||||
.min_key = low_key,
|
||||
.max_key = std::nullopt,
|
||||
.config = Config{},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!machine_contains_shard && shard.size() < label_space.replication_factor) {
|
||||
Address address = storage_manager;
|
||||
|
||||
// TODO(tyler) use deterministic UUID so that coordinators don't diverge here
|
||||
address.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
|
||||
|
||||
ret.push_back(ShardToInitialize{
|
||||
.uuid = address.unique_id,
|
||||
.label_id = label_id,
|
||||
.min_key = low_key,
|
||||
.max_key = std::nullopt,
|
||||
.config = Config{},
|
||||
});
|
||||
|
||||
AddressAndStatus aas = {
|
||||
.address = address,
|
||||
.status = Status::INITIALIZING,
|
||||
};
|
||||
|
||||
shard.emplace_back(aas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
|
||||
if (previous_shard_map_version != shard_map_version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &label_space = label_spaces.at(label_id);
|
||||
auto &shards_in_map = label_space.shards;
|
||||
|
||||
MG_ASSERT(!shards_in_map.empty());
|
||||
MG_ASSERT(!shards_in_map.contains(key));
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
// Finding the Shard that the new PrimaryKey should map to.
|
||||
auto prev = std::prev(shards_in_map.upper_bound(key));
|
||||
Shard duplicated_shard = prev->second;
|
||||
|
||||
// Apply the split
|
||||
shards_in_map[key] = duplicated_shard;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<LabelId> InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
|
||||
size_t replication_factor, Hlc last_shard_map_version);
|
||||
|
||||
void AddServer(Address server_address);
|
||||
void AddServer(Address server_address) {
|
||||
// Find a random place for the server to plug in
|
||||
}
|
||||
|
||||
std::optional<LabelId> GetLabelId(const std::string &label) const;
|
||||
// TODO(antaljanosbenjamin): Remove this and instead use NameIdMapper
|
||||
const std::string &GetLabelName(LabelId label) const;
|
||||
std::optional<PropertyId> GetPropertyId(const std::string &property_name) const;
|
||||
const std::string &GetPropertyName(PropertyId property) const;
|
||||
std::optional<EdgeTypeId> GetEdgeTypeId(const std::string &edge_type) const;
|
||||
const std::string &GetEdgeTypeName(EdgeTypeId property) const;
|
||||
LabelId GetLabelId(const std::string &label) const { return labels.at(label); }
|
||||
|
||||
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const;
|
||||
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const {
|
||||
MG_ASSERT(start_key <= end_key);
|
||||
MG_ASSERT(labels.contains(label_name));
|
||||
|
||||
const Shard &GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const;
|
||||
LabelId label_id = labels.at(label_name);
|
||||
|
||||
const Shard &GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const;
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties);
|
||||
const auto &shards_for_label = label_space.shards;
|
||||
|
||||
EdgeTypeIdMap AllocateEdgeTypeIds(const std::vector<EdgeTypeName> &new_edge_types);
|
||||
MG_ASSERT(shards_for_label.begin()->first <= start_key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
/// Returns true if all shards have the desired number of replicas and they are in
|
||||
/// the CONSENSUS_PARTICIPANT state. Note that this does not necessarily mean that
|
||||
/// there is also an active leader for each shard.
|
||||
bool ClusterInitialized() const;
|
||||
auto it = std::prev(shards_for_label.upper_bound(start_key));
|
||||
const auto end_it = shards_for_label.upper_bound(end_key);
|
||||
|
||||
Shards shards{};
|
||||
|
||||
std::copy(it, end_it, std::inserter(shards, shards.end()));
|
||||
|
||||
return shards;
|
||||
}
|
||||
|
||||
Shard GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
|
||||
MG_ASSERT(labels.contains(label_name));
|
||||
|
||||
LabelId label_id = labels.at(label_name);
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
||||
}
|
||||
|
||||
Shard GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
|
||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
||||
|
||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
||||
}
|
||||
|
||||
PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties) {
|
||||
PropertyMap ret{};
|
||||
|
||||
bool mutated = false;
|
||||
|
||||
for (const auto &property_name : new_properties) {
|
||||
if (properties.contains(property_name)) {
|
||||
auto property_id = properties.at(property_name);
|
||||
ret.emplace(property_name, property_id);
|
||||
} else {
|
||||
mutated = true;
|
||||
|
||||
const PropertyId property_id = PropertyId::FromUint(++max_property_id);
|
||||
ret.emplace(property_name, property_id);
|
||||
properties.emplace(property_name, property_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<PropertyId> GetPropertyId(const std::string &property_name) const {
|
||||
if (properties.contains(property_name)) {
|
||||
return properties.at(property_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#ifndef MG_AST_INCLUDE_PATH
|
||||
#ifdef MG_CLANG_TIDY_CHECK
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#include "query/v2/bindings/bindings.hpp"
|
||||
#define MG_AST_INCLUDE_PATH "query/v2/frontend/ast/ast.hpp"
|
||||
#else
|
||||
#error Missing AST include path
|
||||
#endif
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#ifndef MG_INJECTED_NAMESPACE_NAME
|
||||
#ifdef MG_CLANG_TIDY_CHECK
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define MG_INJECTED_NAMESPACE_NAME memgraph::query::v2
|
||||
#else
|
||||
#error Missing AST namespace
|
||||
#endif
|
||||
|
||||
@@ -1,279 +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 <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "expr/ast.hpp"
|
||||
#include "expr/typed_value.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
inline constexpr const char *identifier_node_symbol = "MG_SYMBOL_NODE";
|
||||
inline constexpr const char *identifier_edge_symbol = "MG_SYMBOL_EDGE";
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const T &arg) {
|
||||
static_assert(!std::is_convertible<T, Expression *>::value,
|
||||
"This overload shouldn't be called with pointers convertible "
|
||||
"to Expression *. This means your other PrintObject overloads aren't "
|
||||
"being called for certain AST nodes when they should (or perhaps such "
|
||||
"overloads don't exist yet).");
|
||||
*out << arg;
|
||||
}
|
||||
|
||||
inline void PrintObject(std::ostream *out, const std::string &str) { *out << str; }
|
||||
|
||||
inline void PrintObject(std::ostream * /*out*/, Aggregation::Op /*op*/) {
|
||||
throw utils::NotYetImplemented("PrintObject: Aggregation::Op");
|
||||
}
|
||||
|
||||
inline void PrintObject(std::ostream * /*out*/, Expression * /*expr*/);
|
||||
|
||||
inline void PrintObject(std::ostream *out, Identifier *expr) { PrintObject(out, static_cast<Expression *>(expr)); }
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream * /*out*/, const std::vector<T> & /*vec*/) {
|
||||
throw utils::NotYetImplemented("PrintObject: vector<T>");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream * /*out*/, const std::vector<T, utils::Allocator<T>> & /*vec*/) {
|
||||
throw utils::NotYetImplemented("PrintObject: vector<T, utils::Allocator<T>>");
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void PrintObject(std::ostream * /*out*/, const std::map<K, V> & /*map*/) {
|
||||
throw utils::NotYetImplemented("PrintObject: map<K, V>");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream * /*out*/, const utils::pmr::map<utils::pmr::string, T> & /*map*/) {
|
||||
throw utils::NotYetImplemented("PrintObject: map<utils::pmr::string, V>");
|
||||
}
|
||||
|
||||
template <typename T1, typename T2, typename T3>
|
||||
inline void PrintObject(std::ostream *out, const TypedValueT<T1, T2, T3> &value) {
|
||||
using TypedValue = TypedValueT<T1, T2, T3>;
|
||||
switch (value.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
*out << "null";
|
||||
break;
|
||||
case TypedValue::Type::String:
|
||||
PrintObject(out, value.ValueString());
|
||||
break;
|
||||
case TypedValue::Type::Bool:
|
||||
*out << (value.ValueBool() ? "true" : "false");
|
||||
break;
|
||||
case TypedValue::Type::Int:
|
||||
PrintObject(out, value.ValueInt());
|
||||
break;
|
||||
case TypedValue::Type::Double:
|
||||
PrintObject(out, value.ValueDouble());
|
||||
break;
|
||||
case TypedValue::Type::List:
|
||||
PrintObject(out, value.ValueList());
|
||||
break;
|
||||
case TypedValue::Type::Map:
|
||||
PrintObject(out, value.ValueMap());
|
||||
break;
|
||||
case TypedValue::Type::Date:
|
||||
PrintObject(out, value.ValueDate());
|
||||
break;
|
||||
case TypedValue::Type::Duration:
|
||||
PrintObject(out, value.ValueDuration());
|
||||
break;
|
||||
case TypedValue::Type::LocalTime:
|
||||
PrintObject(out, value.ValueLocalTime());
|
||||
break;
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
PrintObject(out, value.ValueLocalDateTime());
|
||||
break;
|
||||
default:
|
||||
MG_ASSERT(false, "PrintObject(std::ostream *out, const TypedValue &value) should not reach here");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintOperatorArgs(const std::string & /*name*/, std::ostream *out, bool with_parenthesis, const T &arg) {
|
||||
PrintObject(out, arg);
|
||||
if (with_parenthesis) {
|
||||
*out << ")";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void PrintOperatorArgs(const std::string &name, std::ostream *out, bool with_parenthesis, const T &arg,
|
||||
const Ts &...args) {
|
||||
PrintObject(out, arg);
|
||||
*out << " " << name << " ";
|
||||
PrintOperatorArgs(name, out, with_parenthesis, args...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void PrintOperator(const std::string &name, std::ostream *out, bool with_parenthesis, const Ts &...args) {
|
||||
if (with_parenthesis) {
|
||||
*out << "(";
|
||||
}
|
||||
PrintOperatorArgs(name, out, with_parenthesis, args...);
|
||||
}
|
||||
|
||||
// new
|
||||
template <typename T>
|
||||
void PrintOperatorArgs(std::ostream *out, const T &arg) {
|
||||
PrintObject(out, arg);
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void PrintOperatorArgs(std::ostream *out, const T &arg, const Ts &...args) {
|
||||
PrintObject(out, arg);
|
||||
PrintOperatorArgs(out, args...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void PrintOperator(std::ostream *out, const Ts &...args) {
|
||||
PrintOperatorArgs(out, args...);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
|
||||
public:
|
||||
explicit ExpressionPrettyPrinter(std::ostream *out) : out_(out) {}
|
||||
|
||||
// Unary operators
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define UNARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
void Visit(OP_NODE &op) override { detail::PrintOperator(OP_STR, out_, false /*with_parenthesis*/, op.expression_); }
|
||||
|
||||
UNARY_OPERATOR_VISIT(NotOperator, "Not");
|
||||
UNARY_OPERATOR_VISIT(UnaryPlusOperator, "+");
|
||||
UNARY_OPERATOR_VISIT(UnaryMinusOperator, "-");
|
||||
UNARY_OPERATOR_VISIT(IsNullOperator, "IsNull");
|
||||
|
||||
#undef UNARY_OPERATOR_VISIT
|
||||
|
||||
// Binary operators
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define BINARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
void Visit(OP_NODE &op) override { \
|
||||
detail::PrintOperator(OP_STR, out_, true /*with_parenthesis*/, op.expression1_, op.expression2_); \
|
||||
}
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define BINARY_OPERATOR_VISIT_NOT_IMPL(OP_NODE, OP_STR) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
void Visit(OP_NODE & /*op*/) override { throw utils::NotYetImplemented("OP_NODE"); }
|
||||
|
||||
BINARY_OPERATOR_VISIT(OrOperator, "Or");
|
||||
BINARY_OPERATOR_VISIT(XorOperator, "Xor");
|
||||
BINARY_OPERATOR_VISIT(AndOperator, "And");
|
||||
BINARY_OPERATOR_VISIT(AdditionOperator, "+");
|
||||
BINARY_OPERATOR_VISIT(SubtractionOperator, "-");
|
||||
BINARY_OPERATOR_VISIT(MultiplicationOperator, "*");
|
||||
BINARY_OPERATOR_VISIT(DivisionOperator, "/");
|
||||
BINARY_OPERATOR_VISIT(ModOperator, "%");
|
||||
BINARY_OPERATOR_VISIT(NotEqualOperator, "!=");
|
||||
BINARY_OPERATOR_VISIT(EqualOperator, "=");
|
||||
BINARY_OPERATOR_VISIT(LessOperator, "<");
|
||||
BINARY_OPERATOR_VISIT(GreaterOperator, ">");
|
||||
BINARY_OPERATOR_VISIT(LessEqualOperator, "<=");
|
||||
BINARY_OPERATOR_VISIT(GreaterEqualOperator, ">=");
|
||||
BINARY_OPERATOR_VISIT_NOT_IMPL(InListOperator, "In");
|
||||
BINARY_OPERATOR_VISIT_NOT_IMPL(SubscriptOperator, "Subscript");
|
||||
|
||||
#undef BINARY_OPERATOR_VISIT
|
||||
#undef BINARY_OPERATOR_VISIT_NOT_IMPL
|
||||
|
||||
// Other
|
||||
void Visit(ListSlicingOperator & /*op*/) override { throw utils::NotYetImplemented("ListSlicingOperator"); }
|
||||
|
||||
void Visit(IfOperator & /*op*/) override { throw utils::NotYetImplemented("IfOperator"); }
|
||||
|
||||
void Visit(ListLiteral & /*op*/) override { throw utils::NotYetImplemented("ListLiteral"); }
|
||||
|
||||
void Visit(MapLiteral & /*op*/) override { throw utils::NotYetImplemented("MapLiteral"); }
|
||||
|
||||
void Visit(LabelsTest & /*op*/) override { throw utils::NotYetImplemented("LabelsTest"); }
|
||||
|
||||
void Visit(Aggregation & /*op*/) override { throw utils::NotYetImplemented("Aggregation"); }
|
||||
|
||||
void Visit(Function & /*op*/) override { throw utils::NotYetImplemented("Function"); }
|
||||
|
||||
void Visit(Reduce & /*op*/) override { throw utils::NotYetImplemented("Reduce"); }
|
||||
|
||||
void Visit(Coalesce & /*op*/) override { throw utils::NotYetImplemented("Coalesce"); }
|
||||
|
||||
void Visit(Extract & /*op*/) override { throw utils::NotYetImplemented("Extract"); }
|
||||
|
||||
void Visit(All & /*op*/) override { throw utils::NotYetImplemented("All"); }
|
||||
|
||||
void Visit(Single & /*op*/) override { throw utils::NotYetImplemented("Single"); }
|
||||
|
||||
void Visit(Any & /*op*/) override { throw utils::NotYetImplemented("Any"); }
|
||||
|
||||
void Visit(None & /*op*/) override { throw utils::NotYetImplemented("None"); }
|
||||
|
||||
void Visit(Identifier &op) override {
|
||||
auto is_node = true;
|
||||
auto is_edge = false;
|
||||
auto is_other = false;
|
||||
if (is_node) {
|
||||
detail::PrintOperator(out_, identifier_node_symbol);
|
||||
} else if (is_edge) {
|
||||
detail::PrintOperator(out_, identifier_edge_symbol);
|
||||
} else {
|
||||
MG_ASSERT(is_other);
|
||||
detail::PrintOperator(out_, op.name_);
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(PrimitiveLiteral &op) override { detail::PrintObject(out_, op.value_); }
|
||||
|
||||
void Visit(PropertyLookup &op) override { detail::PrintOperator(out_, op.expression_, ".", op.property_.name); }
|
||||
|
||||
void Visit(ParameterLookup & /*op*/) override { throw utils::NotYetImplemented("ParameterLookup"); }
|
||||
|
||||
void Visit(NamedExpression & /*op*/) override { throw utils::NotYetImplemented("NamedExpression"); }
|
||||
|
||||
void Visit(RegexMatch & /*op*/) override { throw utils::NotYetImplemented("RegexMatch"); }
|
||||
|
||||
private:
|
||||
std::ostream *out_;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
inline void PrintObject(std::ostream *out, Expression *expr) {
|
||||
if (expr) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
} else {
|
||||
*out << "<null>";
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline void PrintExpressionToOriginalAndReplaceNodeAndEdgeSymbols(Expression *expr, std::ostream *out) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
}
|
||||
|
||||
inline std::string ExpressiontoStringWhileReplacingNodeAndEdgeSymbols(Expression *expr) {
|
||||
std::ostringstream ss;
|
||||
expr::PrintExpressionToOriginalAndReplaceNodeAndEdgeSymbols(expr, &ss);
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace memgraph::expr
|
||||
@@ -404,7 +404,6 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
case Error::VERTEX_ALREADY_INSERTED:
|
||||
throw ExpressionRuntimeException("Unexpected error when accessing labels.");
|
||||
}
|
||||
}
|
||||
@@ -719,7 +718,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
TReturnType GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
||||
auto maybe_prop = record_accessor.GetProperty(prop.name);
|
||||
// Handler non existent property
|
||||
return conv_(maybe_prop, dba_);
|
||||
return conv_(maybe_prop);
|
||||
}
|
||||
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
@@ -727,7 +726,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
TReturnType GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
|
||||
auto maybe_prop = record_accessor.GetProperty(std::string(name));
|
||||
// Handler non existent property
|
||||
return conv_(maybe_prop, dba_);
|
||||
return conv_(maybe_prop);
|
||||
}
|
||||
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
@@ -752,7 +751,6 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
case Error::VERTEX_ALREADY_INSERTED:
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
}
|
||||
}
|
||||
@@ -781,7 +779,6 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
case Error::VERTEX_ALREADY_INSERTED:
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class Frame {
|
||||
const TypedValue &at(const Symbol &symbol) const { return elems_.at(symbol.position()); }
|
||||
|
||||
auto &elems() { return elems_; }
|
||||
auto &elems() const { return elems_; }
|
||||
|
||||
utils::MemoryResource *GetMemoryResource() const { return elems_.get_allocator().GetMemoryResource(); }
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "expr/typed_value_exception.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
@@ -33,6 +32,16 @@
|
||||
|
||||
namespace memgraph::expr {
|
||||
|
||||
/**
|
||||
* An exception raised by the TypedValue system. Typically when
|
||||
* trying to perform operations (such as addition) on TypedValues
|
||||
* of incompatible Types.
|
||||
*/
|
||||
class TypedValueException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
// TODO: Neo4j does overflow checking. Should we also implement it?
|
||||
/**
|
||||
* Stores a query runtime value and its type.
|
||||
|
||||
@@ -1,26 +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 "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
/**
|
||||
* An exception raised by the TypedValue system. Typically when
|
||||
* trying to perform operations (such as addition) on TypedValues
|
||||
* of incompatible Types.
|
||||
*/
|
||||
class TypedValueException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
} // namespace memgraph::expr
|
||||
@@ -15,17 +15,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "coordinator/shard_map.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
using memgraph::communication::bolt::Value;
|
||||
@@ -71,52 +63,17 @@ query::v2::TypedValue ToTypedValue(const Value &value) {
|
||||
}
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(
|
||||
const query::v2::accessors::VertexAccessor &vertex, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View /*view*/) {
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
|
||||
auto labels = vertex.Labels();
|
||||
std::vector<std::string> new_labels;
|
||||
new_labels.reserve(labels.size());
|
||||
for (const auto &label : labels) {
|
||||
new_labels.push_back(shard_request_manager->LabelToName(label.id));
|
||||
}
|
||||
|
||||
auto properties = vertex.Properties();
|
||||
std::map<std::string, Value> new_properties;
|
||||
for (const auto &[prop, property_value] : properties) {
|
||||
new_properties[shard_request_manager->PropertyToName(prop)] = ToBoltValue(property_value);
|
||||
}
|
||||
return communication::bolt::Vertex{id, new_labels, new_properties};
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const query::v2::VertexAccessor &vertex,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
return ToBoltVertex(vertex.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(
|
||||
const query::v2::accessors::EdgeAccessor &edge, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View /*view*/) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
auto from = communication::bolt::Id::FromUint(0);
|
||||
auto to = communication::bolt::Id::FromUint(0);
|
||||
const auto &type = shard_request_manager->EdgeTypeToName(edge.EdgeType());
|
||||
|
||||
auto properties = edge.Properties();
|
||||
std::map<std::string, Value> new_properties;
|
||||
for (const auto &[prop, property_value] : properties) {
|
||||
new_properties[shard_request_manager->PropertyToName(prop)] = ToBoltValue(property_value);
|
||||
}
|
||||
return communication::bolt::Edge{id, from, to, type, new_properties};
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const query::v2::EdgeAccessor &edge,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
return ToBoltEdge(edge.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(
|
||||
const query::v2::accessors::Path & /*edge*/, const msgs::ShardRequestManagerInterface * /*shard_request_manager*/,
|
||||
storage::v3::View /*view*/) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
return {storage::v3::Error::DELETED_OBJECT};
|
||||
}
|
||||
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Shard &db,
|
||||
storage::v3::View view) {
|
||||
switch (value.type()) {
|
||||
case query::v2::TypedValue::Type::Null:
|
||||
@@ -133,7 +90,7 @@ storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
std::vector<Value> values;
|
||||
values.reserve(value.ValueList().size());
|
||||
for (const auto &v : value.ValueList()) {
|
||||
auto maybe_value = ToBoltValue(v, shard_request_manager, view);
|
||||
auto maybe_value = ToBoltValue(v, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
values.emplace_back(std::move(*maybe_value));
|
||||
}
|
||||
@@ -142,24 +99,24 @@ storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
case query::v2::TypedValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &kv : value.ValueMap()) {
|
||||
auto maybe_value = ToBoltValue(kv.second, shard_request_manager, view);
|
||||
auto maybe_value = ToBoltValue(kv.second, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
map.emplace(kv.first, std::move(*maybe_value));
|
||||
}
|
||||
return Value(std::move(map));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Vertex: {
|
||||
auto maybe_vertex = ToBoltVertex(value.ValueVertex(), shard_request_manager, view);
|
||||
auto maybe_vertex = ToBoltVertex(value.ValueVertex(), db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
return Value(std::move(*maybe_vertex));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Edge: {
|
||||
auto maybe_edge = ToBoltEdge(value.ValueEdge(), shard_request_manager, view);
|
||||
auto maybe_edge = ToBoltEdge(value.ValueEdge(), db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
return Value(std::move(*maybe_edge));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Path: {
|
||||
auto maybe_path = ToBoltPath(value.ValuePath(), shard_request_manager, view);
|
||||
auto maybe_path = ToBoltPath(value.ValuePath(), db, view);
|
||||
if (maybe_path.HasError()) return maybe_path.GetError();
|
||||
return Value(std::move(*maybe_path));
|
||||
}
|
||||
@@ -174,41 +131,59 @@ storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
}
|
||||
}
|
||||
|
||||
Value ToBoltValue(msgs::Value value) {
|
||||
switch (value.type) {
|
||||
case msgs::Value::Type::Null:
|
||||
return {};
|
||||
case msgs::Value::Type::Bool:
|
||||
return {value.bool_v};
|
||||
case msgs::Value::Type::Int64:
|
||||
return {value.int_v};
|
||||
case msgs::Value::Type::Double:
|
||||
return {value.double_v};
|
||||
case msgs::Value::Type::String:
|
||||
return {std::string(value.string_v)};
|
||||
case msgs::Value::Type::List: {
|
||||
std::vector<Value> values;
|
||||
values.reserve(value.list_v.size());
|
||||
for (const auto &v : value.list_v) {
|
||||
auto maybe_value = ToBoltValue(v);
|
||||
values.emplace_back(std::move(maybe_value));
|
||||
}
|
||||
return Value{std::move(values)};
|
||||
}
|
||||
case msgs::Value::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &kv : value.map_v) {
|
||||
auto maybe_value = ToBoltValue(kv.second);
|
||||
map.emplace(kv.first, std::move(maybe_value));
|
||||
}
|
||||
return Value{std::move(map)};
|
||||
}
|
||||
case msgs::Value::Type::Vertex:
|
||||
case msgs::Value::Type::Edge: {
|
||||
throw utils::BasicException("Vertex and Edge not supported!");
|
||||
}
|
||||
// TODO Value to Date types not supported
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
auto maybe_labels = vertex.Labels(view);
|
||||
if (maybe_labels.HasError()) return maybe_labels.GetError();
|
||||
std::vector<std::string> labels;
|
||||
labels.reserve(maybe_labels->size());
|
||||
for (const auto &label : *maybe_labels) {
|
||||
labels.push_back(db.LabelToName(label));
|
||||
}
|
||||
auto maybe_properties = vertex.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
std::map<std::string, Value> properties;
|
||||
for (const auto &prop : *maybe_properties) {
|
||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||
}
|
||||
return communication::bolt::Vertex{id, labels, properties};
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
// TODO(jbajic) Fix bolt communication
|
||||
auto id = communication::bolt::Id::FromUint(0);
|
||||
auto from = communication::bolt::Id::FromUint(0);
|
||||
auto to = communication::bolt::Id::FromUint(0);
|
||||
const auto &type = db.EdgeTypeToName(edge.EdgeType());
|
||||
auto maybe_properties = edge.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
std::map<std::string, Value> properties;
|
||||
for (const auto &prop : *maybe_properties) {
|
||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||
}
|
||||
return communication::bolt::Edge{id, from, to, type, properties};
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db,
|
||||
storage::v3::View view) {
|
||||
std::vector<communication::bolt::Vertex> vertices;
|
||||
vertices.reserve(path.vertices().size());
|
||||
for (const auto &v : path.vertices()) {
|
||||
auto maybe_vertex = ToBoltVertex(v, db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
vertices.emplace_back(std::move(*maybe_vertex));
|
||||
}
|
||||
std::vector<communication::bolt::Edge> edges;
|
||||
edges.reserve(path.edges().size());
|
||||
for (const auto &e : path.edges()) {
|
||||
auto maybe_edge = ToBoltEdge(e, db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
edges.emplace_back(std::move(*maybe_edge));
|
||||
}
|
||||
return communication::bolt::Path(vertices, edges);
|
||||
}
|
||||
|
||||
storage::v3::PropertyValue ToPropertyValue(const Value &value) {
|
||||
|
||||
@@ -13,12 +13,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "coordinator/shard_map.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
@@ -31,40 +28,36 @@ namespace memgraph::glue::v2 {
|
||||
|
||||
/// @param storage::v3::VertexAccessor for converting to
|
||||
/// communication::bolt::Vertex.
|
||||
/// @param msgs::ShardRequestManagerInterface *shard_request_manager getting label and property names.
|
||||
/// @param storage::v3::Shard for getting label and property names.
|
||||
/// @param storage::v3::View for deciding which vertex attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(
|
||||
const storage::v3::VertexAccessor &vertex, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View view);
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
/// @param storage::v3::EdgeAccessor for converting to communication::bolt::Edge.
|
||||
/// @param msgs::ShardRequestManagerInterface *shard_request_manager getting edge type and property names.
|
||||
/// @param storage::v3::Shard for getting edge type and property names.
|
||||
/// @param storage::v3::View for deciding which edge attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(
|
||||
const storage::v3::EdgeAccessor &edge, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View view);
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
/// @param query::v2::Path for converting to communication::bolt::Path.
|
||||
/// @param msgs::ShardRequestManagerInterface *shard_request_manager ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(
|
||||
const query::v2::accessors::Path &path, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View view);
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db,
|
||||
storage::v3::View view);
|
||||
|
||||
/// @param query::v2::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param msgs::ShardRequestManagerInterface *shard_request_manager ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Value> ToBoltValue(
|
||||
const query::v2::TypedValue &value, const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View view);
|
||||
storage::v3::Result<communication::bolt::Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
const storage::v3::Shard &db, storage::v3::View view);
|
||||
|
||||
query::v2::TypedValue ToTypedValue(const communication::bolt::Value &value);
|
||||
|
||||
@@ -72,10 +65,4 @@ communication::bolt::Value ToBoltValue(const storage::v3::PropertyValue &value);
|
||||
|
||||
storage::v3::PropertyValue ToPropertyValue(const communication::bolt::Value &value);
|
||||
|
||||
communication::bolt::Value ToBoltValue(msgs::Value value);
|
||||
|
||||
communication::bolt::Value ToBoltValue(msgs::Value value,
|
||||
const msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
storage::v3::View view);
|
||||
|
||||
} // namespace memgraph::glue::v2
|
||||
|
||||
@@ -15,36 +15,11 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
namespace memgraph::io {
|
||||
|
||||
struct PartialAddress {
|
||||
boost::asio::ip::address ip;
|
||||
uint16_t port;
|
||||
|
||||
friend bool operator==(const PartialAddress &lhs, const PartialAddress &rhs) = default;
|
||||
|
||||
/// unique_id is most dominant for ordering, then ip, then port
|
||||
friend bool operator<(const PartialAddress &lhs, const PartialAddress &rhs) {
|
||||
if (lhs.ip != rhs.ip) {
|
||||
return lhs.ip < rhs.ip;
|
||||
}
|
||||
|
||||
return lhs.port < rhs.port;
|
||||
}
|
||||
|
||||
std::string ToString() const { return fmt::format("PartialAddress {{ ip: {}, port: {} }}", ip.to_string(), port); }
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const PartialAddress &partial_address) {
|
||||
in << partial_address.ToString();
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
struct Address {
|
||||
// It's important for all participants to have a
|
||||
// unique identifier - IP and port alone are not
|
||||
@@ -79,13 +54,6 @@ struct Address {
|
||||
};
|
||||
}
|
||||
|
||||
PartialAddress ToPartialAddress() const {
|
||||
return PartialAddress{
|
||||
.ip = last_known_ip,
|
||||
.port = last_known_port,
|
||||
};
|
||||
}
|
||||
|
||||
friend bool operator==(const Address &lhs, const Address &rhs) = default;
|
||||
|
||||
/// unique_id is most dominant for ordering, then last_known_ip, then last_known_port
|
||||
@@ -105,36 +73,5 @@ struct Address {
|
||||
return fmt::format("Address {{ unique_id: {}, last_known_ip: {}, last_known_port: {} }}",
|
||||
boost::uuids::to_string(unique_id), last_known_ip.to_string(), last_known_port);
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const Address &address) {
|
||||
in << address.ToString();
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace memgraph::io
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<memgraph::io::PartialAddress> {
|
||||
size_t operator()(const memgraph::io::PartialAddress &pa) const {
|
||||
using boost::hash_combine;
|
||||
using boost::hash_value;
|
||||
|
||||
// Start with a hash value of 0 .
|
||||
std::size_t seed = 0;
|
||||
|
||||
if (pa.ip.is_v4()) {
|
||||
auto h = std::hash<boost::asio::ip::address_v4>()(pa.ip.to_v4());
|
||||
hash_combine(seed, h);
|
||||
} else {
|
||||
auto h = std::hash<boost::asio::ip::address_v6>()(pa.ip.to_v6());
|
||||
hash_combine(seed, h);
|
||||
}
|
||||
hash_combine(seed, hash_value(pa.port));
|
||||
|
||||
// Return the result.
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
@@ -148,14 +148,6 @@ class Future {
|
||||
old.consumed_or_moved_ = true;
|
||||
}
|
||||
|
||||
Future &operator=(Future &&old) noexcept {
|
||||
MG_ASSERT(!old.consumed_or_moved_, "Future moved from after already being moved from or consumed.");
|
||||
shared_ = std::move(old.shared_);
|
||||
consumed_or_moved_ = old.consumed_or_moved_;
|
||||
old.consumed_or_moved_ = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Future(const Future &) = delete;
|
||||
Future &operator=(const Future &) = delete;
|
||||
~Future() = default;
|
||||
@@ -200,7 +192,7 @@ class Future {
|
||||
template <typename T>
|
||||
class Promise {
|
||||
std::shared_ptr<details::Shared<T>> shared_;
|
||||
bool filled_or_moved_{false};
|
||||
bool filled_or_moved_ = false;
|
||||
|
||||
public:
|
||||
explicit Promise(std::shared_ptr<details::Shared<T>> shared) : shared_(shared) {}
|
||||
@@ -220,7 +212,6 @@ class Promise {
|
||||
Promise(const Promise &) = delete;
|
||||
Promise &operator=(const Promise &) = delete;
|
||||
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
|
||||
~Promise() { MG_ASSERT(filled_or_moved_, "Promise destroyed before its associated Future was filled!"); }
|
||||
|
||||
// Fill the expected item into the Future.
|
||||
|
||||
@@ -31,9 +31,14 @@ class LocalTransport {
|
||||
: local_transport_handle_(std::move(local_transport_handle)) {}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestT request, Duration timeout) {
|
||||
return local_transport_handle_->template SubmitRequest<RequestT, ResponseT>(to_address, from_address,
|
||||
std::move(request), timeout);
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestId request_id, RequestT request,
|
||||
Duration timeout) {
|
||||
auto [future, promise] = memgraph::io::FuturePromisePair<ResponseResult<ResponseT>>();
|
||||
|
||||
local_transport_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
|
||||
std::move(promise));
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
@@ -55,7 +60,5 @@ class LocalTransport {
|
||||
std::random_device rng;
|
||||
return distrib(rng);
|
||||
}
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies() { return local_transport_handle_->ResponseLatencies(); }
|
||||
};
|
||||
}; // namespace memgraph::io::local_transport
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "io/errors.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/message_histogram_collector.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
@@ -29,8 +28,6 @@ class LocalTransportHandle {
|
||||
mutable std::mutex mu_{};
|
||||
mutable std::condition_variable cv_;
|
||||
bool should_shut_down_ = false;
|
||||
MessageHistogramCollector histograms_;
|
||||
RequestId request_id_counter_ = 0;
|
||||
|
||||
// the responses to requests that are being waited on
|
||||
std::map<PromiseKey, DeadlineAndOpaquePromise> promises_;
|
||||
@@ -57,11 +54,6 @@ class LocalTransportHandle {
|
||||
return should_shut_down_;
|
||||
}
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
return histograms_.ResponseLatencies();
|
||||
}
|
||||
|
||||
static Time Now() {
|
||||
auto nano_time = std::chrono::system_clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::microseconds>(nano_time);
|
||||
@@ -105,16 +97,14 @@ class LocalTransportHandle {
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
|
||||
auto type_info = TypeInfoFor(message);
|
||||
|
||||
std::any message_any(std::forward<M>(message));
|
||||
OpaqueMessage opaque_message{.to_address = to_address,
|
||||
.from_address = from_address,
|
||||
.request_id = request_id,
|
||||
.message = std::move(message_any),
|
||||
.type_info = type_info};
|
||||
.message = std::move(message_any)};
|
||||
|
||||
PromiseKey promise_key{.requester_address = to_address, .request_id = opaque_message.request_id};
|
||||
PromiseKey promise_key{
|
||||
.requester_address = to_address, .request_id = opaque_message.request_id, .replier_address = from_address};
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
@@ -125,10 +115,7 @@ class LocalTransportHandle {
|
||||
DeadlineAndOpaquePromise dop = std::move(promises_.at(promise_key));
|
||||
promises_.erase(promise_key);
|
||||
|
||||
Duration response_latency = Now() - dop.requested_at;
|
||||
|
||||
dop.promise.Fill(std::move(opaque_message), response_latency);
|
||||
histograms_.Measure(type_info, response_latency);
|
||||
dop.promise.Fill(std::move(opaque_message));
|
||||
} else {
|
||||
spdlog::info("placing message in can_receive_");
|
||||
can_receive_.emplace_back(std::move(opaque_message));
|
||||
@@ -139,34 +126,25 @@ class LocalTransportHandle {
|
||||
}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> SubmitRequest(Address to_address, Address from_address, RequestT &&request,
|
||||
Duration timeout) {
|
||||
auto [future, promise] = memgraph::io::FuturePromisePair<ResponseResult<ResponseT>>();
|
||||
|
||||
void SubmitRequest(Address to_address, Address from_address, RequestId request_id, RequestT &&request,
|
||||
Duration timeout, ResponsePromise<ResponseT> promise) {
|
||||
const bool port_matches = to_address.last_known_port == from_address.last_known_port;
|
||||
const bool ip_matches = to_address.last_known_ip == from_address.last_known_ip;
|
||||
|
||||
MG_ASSERT(port_matches && ip_matches);
|
||||
const Time deadline = Now() + timeout;
|
||||
|
||||
const auto now = Now();
|
||||
const Time deadline = now + timeout;
|
||||
|
||||
RequestId request_id = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
request_id = ++request_id_counter_;
|
||||
PromiseKey promise_key{.requester_address = from_address, .request_id = request_id};
|
||||
PromiseKey promise_key{
|
||||
.requester_address = from_address, .request_id = request_id, .replier_address = to_address};
|
||||
OpaquePromise opaque_promise(std::move(promise).ToUnique());
|
||||
DeadlineAndOpaquePromise dop{.requested_at = now, .deadline = deadline, .promise = std::move(opaque_promise)};
|
||||
|
||||
MG_ASSERT(!promises_.contains(promise_key));
|
||||
DeadlineAndOpaquePromise dop{.deadline = deadline, .promise = std::move(opaque_promise)};
|
||||
promises_.emplace(std::move(promise_key), std::move(dop));
|
||||
} // lock dropped
|
||||
|
||||
Send(to_address, from_address, request_id, std::forward<RequestT>(request));
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,16 +11,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
|
||||
#include "io/transport.hpp"
|
||||
#include "utils/type_info_ref.hpp"
|
||||
|
||||
namespace memgraph::io {
|
||||
|
||||
using memgraph::io::Duration;
|
||||
using memgraph::io::Message;
|
||||
using memgraph::io::Time;
|
||||
|
||||
struct PromiseKey {
|
||||
Address requester_address;
|
||||
uint64_t request_id;
|
||||
// TODO(tyler) possibly remove replier_address from promise key
|
||||
// once we want to support DSR.
|
||||
Address replier_address;
|
||||
|
||||
public:
|
||||
friend bool operator<(const PromiseKey &lhs, const PromiseKey &rhs) {
|
||||
@@ -28,7 +32,11 @@ struct PromiseKey {
|
||||
return lhs.requester_address < rhs.requester_address;
|
||||
}
|
||||
|
||||
return lhs.request_id < rhs.request_id;
|
||||
if (lhs.request_id != rhs.request_id) {
|
||||
return lhs.request_id < rhs.request_id;
|
||||
}
|
||||
|
||||
return lhs.replier_address < rhs.replier_address;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,7 +45,6 @@ struct OpaqueMessage {
|
||||
Address from_address;
|
||||
uint64_t request_id;
|
||||
std::any message;
|
||||
utils::TypeInfoRef type_info;
|
||||
|
||||
/// Recursively tries to match a specific type from the outer
|
||||
/// variant's parameter pack against the type of the std::any,
|
||||
@@ -85,10 +92,6 @@ struct OpaqueMessage {
|
||||
};
|
||||
}
|
||||
|
||||
std::string demangled_name = "\"" + boost::core::demangle(message.type().name()) + "\"";
|
||||
spdlog::error("failed to cast message of type {} to expected request type (probably in Receive argument types)",
|
||||
demangled_name);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
@@ -97,7 +100,7 @@ class OpaquePromiseTraitBase {
|
||||
public:
|
||||
virtual const std::type_info *TypeInfo() const = 0;
|
||||
virtual bool IsAwaited(void *ptr) const = 0;
|
||||
virtual void Fill(void *ptr, OpaqueMessage &&, Duration) const = 0;
|
||||
virtual void Fill(void *ptr, OpaqueMessage &&) const = 0;
|
||||
virtual void TimeOut(void *ptr) const = 0;
|
||||
|
||||
virtual ~OpaquePromiseTraitBase() = default;
|
||||
@@ -115,13 +118,12 @@ class OpaquePromiseTrait : public OpaquePromiseTraitBase {
|
||||
|
||||
bool IsAwaited(void *ptr) const override { return static_cast<ResponsePromise<T> *>(ptr)->IsAwaited(); };
|
||||
|
||||
void Fill(void *ptr, OpaqueMessage &&opaque_message, Duration response_latency) const override {
|
||||
void Fill(void *ptr, OpaqueMessage &&opaque_message) const override {
|
||||
T message = std::any_cast<T>(std::move(opaque_message.message));
|
||||
auto response_envelope = ResponseEnvelope<T>{.message = std::move(message),
|
||||
.request_id = opaque_message.request_id,
|
||||
.to_address = opaque_message.to_address,
|
||||
.from_address = opaque_message.from_address,
|
||||
.response_latency = response_latency};
|
||||
.from_address = opaque_message.from_address};
|
||||
auto promise = static_cast<ResponsePromise<T> *>(ptr);
|
||||
auto unique_promise = std::unique_ptr<ResponsePromise<T>>(promise);
|
||||
unique_promise->Fill(std::move(response_envelope));
|
||||
@@ -185,9 +187,9 @@ class OpaquePromise {
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
|
||||
void Fill(OpaqueMessage &&opaque_message, Duration response_latency) {
|
||||
void Fill(OpaqueMessage &&opaque_message) {
|
||||
MG_ASSERT(ptr_ != nullptr);
|
||||
trait_->Fill(ptr_, std::move(opaque_message), response_latency);
|
||||
trait_->Fill(ptr_, std::move(opaque_message));
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
|
||||
@@ -197,24 +199,18 @@ class OpaquePromise {
|
||||
};
|
||||
|
||||
struct DeadlineAndOpaquePromise {
|
||||
Time requested_at;
|
||||
Time deadline;
|
||||
OpaquePromise promise;
|
||||
};
|
||||
|
||||
template <class From>
|
||||
std::type_info const &TypeInfoForVariant(From const &from) {
|
||||
std::type_info const &type_info_for_variant(From const &from) {
|
||||
return std::visit([](auto &&x) -> decltype(auto) { return typeid(x); }, from);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
utils::TypeInfoRef TypeInfoFor(const T & /* t */) {
|
||||
return typeid(T);
|
||||
}
|
||||
|
||||
template <typename From, typename Return, typename Head, typename... Rest>
|
||||
std::optional<Return> ConvertVariantInner(From &&a) {
|
||||
if (typeid(Head) == TypeInfoForVariant(a)) {
|
||||
if (typeid(Head) == type_info_for_variant(a)) {
|
||||
Head concrete = std::get<Head>(std::forward<From>(a));
|
||||
return concrete;
|
||||
}
|
||||
|
||||
@@ -1,127 +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 <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
|
||||
#include "io/time.hpp"
|
||||
#include "utils/histogram.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/print_helpers.hpp"
|
||||
#include "utils/type_info_ref.hpp"
|
||||
|
||||
namespace memgraph::io {
|
||||
|
||||
struct LatencyHistogramSummary {
|
||||
uint64_t count;
|
||||
Duration p0;
|
||||
Duration p50;
|
||||
Duration p75;
|
||||
Duration p90;
|
||||
Duration p95;
|
||||
Duration p975;
|
||||
Duration p99;
|
||||
Duration p999;
|
||||
Duration p9999;
|
||||
Duration p100;
|
||||
Duration sum;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const LatencyHistogramSummary &histo) {
|
||||
in << "{ \"count\": " << histo.count;
|
||||
in << ", \"p0\": " << histo.p0.count();
|
||||
in << ", \"p50\": " << histo.p50.count();
|
||||
in << ", \"p75\": " << histo.p75.count();
|
||||
in << ", \"p90\": " << histo.p90.count();
|
||||
in << ", \"p95\": " << histo.p95.count();
|
||||
in << ", \"p975\": " << histo.p975.count();
|
||||
in << ", \"p99\": " << histo.p99.count();
|
||||
in << ", \"p999\": " << histo.p999.count();
|
||||
in << ", \"p9999\": " << histo.p9999.count();
|
||||
in << ", \"p100\": " << histo.p100.count();
|
||||
in << ", \"sum\": " << histo.sum.count();
|
||||
in << " }";
|
||||
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
struct LatencyHistogramSummaries {
|
||||
std::unordered_map<std::string, LatencyHistogramSummary> latencies;
|
||||
|
||||
std::string SummaryTable() {
|
||||
std::string output;
|
||||
|
||||
const auto row = [&output](const auto &c1, const auto &c2, const auto &c3, const auto &c4, const auto &c5,
|
||||
const auto &c6, const auto &c7) {
|
||||
output +=
|
||||
fmt::format("{: >50} | {: >8} | {: >8} | {: >8} | {: >8} | {: >8} | {: >8}\n", c1, c2, c3, c4, c5, c6, c7);
|
||||
};
|
||||
row("name", "count", "min (μs)", "med (μs)", "p99 (μs)", "max (μs)", "sum (ms)");
|
||||
|
||||
for (const auto &[name, histo] : latencies) {
|
||||
row(name, histo.count, histo.p0.count(), histo.p50.count(), histo.p99.count(), histo.p100.count(),
|
||||
histo.sum.count() / 1000);
|
||||
}
|
||||
|
||||
output += "\n";
|
||||
return output;
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &in, const LatencyHistogramSummaries &histo) {
|
||||
using memgraph::utils::print_helpers::operator<<;
|
||||
in << histo.latencies;
|
||||
return in;
|
||||
}
|
||||
};
|
||||
|
||||
class MessageHistogramCollector {
|
||||
std::unordered_map<utils::TypeInfoRef, utils::Histogram, utils::TypeInfoHasher, utils::TypeInfoEqualTo> histograms_;
|
||||
|
||||
public:
|
||||
void Measure(const std::type_info &type_info, const Duration &duration) {
|
||||
auto &histo = histograms_[type_info];
|
||||
histo.Measure(duration.count());
|
||||
}
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies() {
|
||||
std::unordered_map<std::string, LatencyHistogramSummary> ret{};
|
||||
|
||||
for (const auto &[type_id, histo] : histograms_) {
|
||||
std::string demangled_name = "\"" + boost::core::demangle(type_id.get().name()) + "\"";
|
||||
|
||||
LatencyHistogramSummary latency_histogram_summary{
|
||||
.count = histo.Count(),
|
||||
.p0 = Duration(static_cast<int64_t>(histo.Percentile(0.0))),
|
||||
.p50 = Duration(static_cast<int64_t>(histo.Percentile(50.0))),
|
||||
.p75 = Duration(static_cast<int64_t>(histo.Percentile(75.0))),
|
||||
.p90 = Duration(static_cast<int64_t>(histo.Percentile(90.0))),
|
||||
.p95 = Duration(static_cast<int64_t>(histo.Percentile(95.0))),
|
||||
.p975 = Duration(static_cast<int64_t>(histo.Percentile(97.5))),
|
||||
.p99 = Duration(static_cast<int64_t>(histo.Percentile(99.0))),
|
||||
.p999 = Duration(static_cast<int64_t>(histo.Percentile(99.9))),
|
||||
.p9999 = Duration(static_cast<int64_t>(histo.Percentile(99.99))),
|
||||
.p100 = Duration(static_cast<int64_t>(histo.Percentile(100.0))),
|
||||
.sum = Duration(histo.Sum()),
|
||||
};
|
||||
|
||||
ret.emplace(demangled_name, latency_histogram_summary);
|
||||
}
|
||||
|
||||
return LatencyHistogramSummaries{.latencies = ret};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::io
|
||||
@@ -22,9 +22,6 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/simulator/simulator.hpp"
|
||||
#include "io/transport.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
@@ -91,36 +88,6 @@ struct ReadResponse {
|
||||
std::optional<Address> retry_leader;
|
||||
};
|
||||
|
||||
template <class... ReadReturn>
|
||||
utils::TypeInfoRef TypeInfoFor(const ReadResponse<std::variant<ReadReturn...>> &read_response) {
|
||||
return TypeInfoForVariant(read_response.read_return);
|
||||
}
|
||||
|
||||
template <class ReadReturn>
|
||||
utils::TypeInfoRef TypeInfoFor(const ReadResponse<ReadReturn> & /* read_response */) {
|
||||
return typeid(ReadReturn);
|
||||
}
|
||||
|
||||
template <class... WriteReturn>
|
||||
utils::TypeInfoRef TypeInfoFor(const WriteResponse<std::variant<WriteReturn...>> &write_response) {
|
||||
return TypeInfoForVariant(write_response.write_return);
|
||||
}
|
||||
|
||||
template <class WriteReturn>
|
||||
utils::TypeInfoRef TypeInfoFor(const WriteResponse<WriteReturn> & /* write_response */) {
|
||||
return typeid(WriteReturn);
|
||||
}
|
||||
|
||||
template <class WriteOperation>
|
||||
utils::TypeInfoRef TypeInfoFor(const WriteRequest<WriteOperation> & /* write_request */) {
|
||||
return typeid(WriteOperation);
|
||||
}
|
||||
|
||||
template <class... WriteOperations>
|
||||
utils::TypeInfoRef TypeInfoFor(const WriteRequest<std::variant<WriteOperations...>> &write_request) {
|
||||
return TypeInfoForVariant(write_request.operation);
|
||||
}
|
||||
|
||||
/// AppendRequest is a raft-level message that the Leader
|
||||
/// periodically broadcasts to all Follower peers. This
|
||||
/// serves three main roles:
|
||||
@@ -581,7 +548,7 @@ class Raft {
|
||||
const Time now = io_.Now();
|
||||
const Duration broadcast_timeout = RandomTimeout(kMinimumBroadcastTimeout, kMaximumBroadcastTimeout);
|
||||
|
||||
if (now > leader.last_broadcast + broadcast_timeout) {
|
||||
if (now - leader.last_broadcast > broadcast_timeout) {
|
||||
BroadcastAppendEntries(leader.followers);
|
||||
leader.last_broadcast = now;
|
||||
}
|
||||
@@ -930,9 +897,7 @@ class Raft {
|
||||
// only leaders actually handle replication requests from clients
|
||||
std::optional<Role> Handle(Leader &leader, WriteRequest<WriteOperation> &&req, RequestId request_id,
|
||||
Address from_address) {
|
||||
auto type_info = TypeInfoFor(req);
|
||||
std::string demangled_name = boost::core::demangle(type_info.get().name());
|
||||
Log("handling WriteRequest<" + demangled_name + ">");
|
||||
Log("handling WriteRequest");
|
||||
|
||||
// we are the leader. add item to log and send Append to peers
|
||||
MG_ASSERT(state_.term >= LastLogTerm());
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/errors.hpp"
|
||||
#include "io/rsm/raft.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
@@ -45,36 +43,21 @@ class RsmClient {
|
||||
Address leader_;
|
||||
ServerPool server_addrs_;
|
||||
|
||||
/// State for single async read/write operations. In the future this could become a map
|
||||
/// of async operations that can be accessed via an ID etc...
|
||||
std::optional<Time> async_read_before_;
|
||||
std::optional<ResponseFuture<ReadResponse<ReadResponseT>>> async_read_;
|
||||
ReadRequestT current_read_request_;
|
||||
|
||||
std::optional<Time> async_write_before_;
|
||||
std::optional<ResponseFuture<WriteResponse<WriteResponseT>>> async_write_;
|
||||
WriteRequestT current_write_request_;
|
||||
|
||||
void SelectRandomLeader() {
|
||||
std::uniform_int_distribution<size_t> addr_distrib(0, (server_addrs_.size() - 1));
|
||||
size_t addr_index = io_.Rand(addr_distrib);
|
||||
leader_ = server_addrs_[addr_index];
|
||||
|
||||
spdlog::debug(
|
||||
"client NOT redirected to leader server despite our success failing to be processed (it probably was sent to "
|
||||
"a RSM Candidate) trying a random one at index {} with address {}",
|
||||
addr_index, leader_.ToString());
|
||||
}
|
||||
|
||||
template <typename ResponseT>
|
||||
void PossiblyRedirectLeader(const ResponseT &response) {
|
||||
if (response.retry_leader) {
|
||||
MG_ASSERT(!response.success, "retry_leader should never be set for successful responses");
|
||||
leader_ = response.retry_leader.value();
|
||||
spdlog::debug("client redirected to leader server {}", leader_.ToString());
|
||||
}
|
||||
if (!response.success) {
|
||||
SelectRandomLeader();
|
||||
} else if (!response.success) {
|
||||
std::uniform_int_distribution<size_t> addr_distrib(0, (server_addrs_.size() - 1));
|
||||
size_t addr_index = io_.Rand(addr_distrib);
|
||||
leader_ = server_addrs_[addr_index];
|
||||
|
||||
spdlog::debug(
|
||||
"client NOT redirected to leader server despite our success failing to be processed (it probably was sent to "
|
||||
"a RSM Candidate) trying a random one at index {} with address {}",
|
||||
addr_index, leader_.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,13 +65,7 @@ class RsmClient {
|
||||
RsmClient(Io<IoImpl> io, Address leader, ServerPool server_addrs)
|
||||
: io_{io}, leader_{leader}, server_addrs_{server_addrs} {}
|
||||
|
||||
RsmClient(const RsmClient &) = delete;
|
||||
RsmClient &operator=(const RsmClient &) = delete;
|
||||
RsmClient(RsmClient &&) noexcept = default;
|
||||
RsmClient &operator=(RsmClient &&) noexcept = default;
|
||||
|
||||
RsmClient() = delete;
|
||||
~RsmClient() = default;
|
||||
|
||||
BasicResult<TimedOut, WriteResponseT> SendWriteRequest(WriteRequestT req) {
|
||||
WriteRequest<WriteRequestT> client_req;
|
||||
@@ -154,117 +131,6 @@ class RsmClient {
|
||||
|
||||
return TimedOut{};
|
||||
}
|
||||
|
||||
/// AsyncRead methods
|
||||
void SendAsyncReadRequest(const ReadRequestT &req) {
|
||||
MG_ASSERT(!async_read_);
|
||||
|
||||
ReadRequest<ReadRequestT> read_req = {.operation = req};
|
||||
|
||||
if (!async_read_before_) {
|
||||
async_read_before_ = io_.Now();
|
||||
}
|
||||
current_read_request_ = std::move(req);
|
||||
async_read_ = io_.template Request<ReadRequest<ReadRequestT>, ReadResponse<ReadResponseT>>(leader_, read_req);
|
||||
}
|
||||
|
||||
std::optional<BasicResult<TimedOut, ReadResponseT>> PollAsyncReadRequest() {
|
||||
MG_ASSERT(async_read_);
|
||||
|
||||
if (!async_read_->IsReady()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return AwaitAsyncReadRequest();
|
||||
}
|
||||
|
||||
std::optional<BasicResult<TimedOut, ReadResponseT>> AwaitAsyncReadRequest() {
|
||||
ResponseResult<ReadResponse<ReadResponseT>> get_response_result = std::move(*async_read_).Wait();
|
||||
async_read_.reset();
|
||||
|
||||
const Duration overall_timeout = io_.GetDefaultTimeout();
|
||||
const bool past_time_out = io_.Now() < *async_read_before_ + overall_timeout;
|
||||
const bool result_has_error = get_response_result.HasError();
|
||||
|
||||
if (result_has_error && past_time_out) {
|
||||
// TODO static assert the exact type of error.
|
||||
spdlog::debug("client timed out while trying to communicate with leader server {}", leader_.ToString());
|
||||
async_read_before_ = std::nullopt;
|
||||
return TimedOut{};
|
||||
}
|
||||
if (!result_has_error) {
|
||||
ResponseEnvelope<ReadResponse<ReadResponseT>> &&get_response_envelope = std::move(get_response_result.GetValue());
|
||||
ReadResponse<ReadResponseT> &&read_get_response = std::move(get_response_envelope.message);
|
||||
|
||||
PossiblyRedirectLeader(read_get_response);
|
||||
|
||||
if (read_get_response.success) {
|
||||
async_read_before_ = std::nullopt;
|
||||
return std::move(read_get_response.read_return);
|
||||
}
|
||||
SendAsyncReadRequest(current_read_request_);
|
||||
} else if (result_has_error) {
|
||||
SelectRandomLeader();
|
||||
SendAsyncReadRequest(current_read_request_);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/// AsyncWrite methods
|
||||
void SendAsyncWriteRequest(const WriteRequestT &req) {
|
||||
MG_ASSERT(!async_write_);
|
||||
|
||||
WriteRequest<WriteRequestT> write_req = {.operation = req};
|
||||
|
||||
if (!async_write_before_) {
|
||||
async_write_before_ = io_.Now();
|
||||
}
|
||||
current_write_request_ = std::move(req);
|
||||
async_write_ = io_.template Request<WriteRequest<WriteRequestT>, WriteResponse<WriteResponseT>>(leader_, write_req);
|
||||
}
|
||||
|
||||
std::optional<BasicResult<TimedOut, WriteResponseT>> PollAsyncWriteRequest() {
|
||||
MG_ASSERT(async_write_);
|
||||
|
||||
if (!async_write_->IsReady()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return AwaitAsyncWriteRequest();
|
||||
}
|
||||
|
||||
std::optional<BasicResult<TimedOut, WriteResponseT>> AwaitAsyncWriteRequest() {
|
||||
ResponseResult<WriteResponse<WriteResponseT>> get_response_result = std::move(*async_write_).Wait();
|
||||
async_write_.reset();
|
||||
|
||||
const Duration overall_timeout = io_.GetDefaultTimeout();
|
||||
const bool past_time_out = io_.Now() < *async_write_before_ + overall_timeout;
|
||||
const bool result_has_error = get_response_result.HasError();
|
||||
|
||||
if (result_has_error && past_time_out) {
|
||||
// TODO static assert the exact type of error.
|
||||
spdlog::debug("client timed out while trying to communicate with leader server {}", leader_.ToString());
|
||||
async_write_before_ = std::nullopt;
|
||||
return TimedOut{};
|
||||
}
|
||||
if (!result_has_error) {
|
||||
ResponseEnvelope<WriteResponse<WriteResponseT>> &&get_response_envelope =
|
||||
std::move(get_response_result.GetValue());
|
||||
WriteResponse<WriteResponseT> &&write_get_response = std::move(get_response_envelope.message);
|
||||
|
||||
PossiblyRedirectLeader(write_get_response);
|
||||
|
||||
if (write_get_response.success) {
|
||||
async_write_before_ = std::nullopt;
|
||||
return std::move(write_get_response.write_return);
|
||||
}
|
||||
SendAsyncWriteRequest(current_write_request_);
|
||||
} else if (result_has_error) {
|
||||
SelectRandomLeader();
|
||||
SendAsyncWriteRequest(current_write_request_);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::io::rsm
|
||||
|
||||
@@ -29,8 +29,6 @@ class Simulator {
|
||||
explicit Simulator(SimulatorConfig config)
|
||||
: rng_(std::mt19937{config.rng_seed}), simulator_handle_{std::make_shared<SimulatorHandle>(config)} {}
|
||||
|
||||
~Simulator() { ShutDown(); }
|
||||
|
||||
void ShutDown() { simulator_handle_->ShutDown(); }
|
||||
|
||||
Io<SimulatorTransport> RegisterNew() {
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
#include "io/simulator/simulator_stats.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::io::simulator {
|
||||
|
||||
using memgraph::io::Duration;
|
||||
using memgraph::io::Time;
|
||||
|
||||
void SimulatorHandle::ShutDown() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
should_shut_down_ = true;
|
||||
@@ -31,11 +33,6 @@ bool SimulatorHandle::ShouldShutDown() const {
|
||||
return should_shut_down_;
|
||||
}
|
||||
|
||||
LatencyHistogramSummaries SimulatorHandle::ResponseLatencies() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
return histograms_.ResponseLatencies();
|
||||
}
|
||||
|
||||
void SimulatorHandle::IncrementServerCountAndWaitForQuiescentState(Address address) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
server_addresses_.insert(address);
|
||||
@@ -76,18 +73,13 @@ bool SimulatorHandle::MaybeTickSimulator() {
|
||||
|
||||
// We tick the clock forward when all servers are blocked but
|
||||
// there are no in-flight messages to schedule delivery of.
|
||||
const Duration clock_advance = std::chrono::microseconds{time_distrib_(rng_)};
|
||||
std::poisson_distribution<> time_distrib(50);
|
||||
Duration clock_advance = std::chrono::microseconds{time_distrib(rng_)};
|
||||
cluster_wide_time_microseconds_ += clock_advance;
|
||||
|
||||
if (cluster_wide_time_microseconds_ >= config_.abort_time) {
|
||||
if (should_shut_down_) {
|
||||
return false;
|
||||
}
|
||||
spdlog::error(
|
||||
"Cluster has executed beyond its configured abort_time, and something may be failing to make progress "
|
||||
"in an expected amount of time.");
|
||||
throw utils::BasicException{"Cluster has executed beyond its configured abort_time"};
|
||||
}
|
||||
MG_ASSERT(cluster_wide_time_microseconds_ < config_.abort_time,
|
||||
"Cluster has executed beyond its configured abort_time, and something may be failing to make progress "
|
||||
"in an expected amount of time.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,14 +93,17 @@ bool SimulatorHandle::MaybeTickSimulator() {
|
||||
auto [to_address, opaque_message] = std::move(in_flight_.back());
|
||||
in_flight_.pop_back();
|
||||
|
||||
const int drop_threshold = drop_distrib_(rng_);
|
||||
std::uniform_int_distribution<int> drop_distrib(0, 99);
|
||||
const int drop_threshold = drop_distrib(rng_);
|
||||
const bool should_drop = drop_threshold < config_.drop_percent;
|
||||
|
||||
if (should_drop) {
|
||||
stats_.dropped_messages++;
|
||||
}
|
||||
|
||||
PromiseKey promise_key{.requester_address = to_address, .request_id = opaque_message.request_id};
|
||||
PromiseKey promise_key{.requester_address = to_address,
|
||||
.request_id = opaque_message.request_id,
|
||||
.replier_address = opaque_message.from_address};
|
||||
|
||||
if (promises_.contains(promise_key)) {
|
||||
// complete waiting promise if it's there
|
||||
@@ -122,17 +117,13 @@ bool SimulatorHandle::MaybeTickSimulator() {
|
||||
dop.promise.TimeOut();
|
||||
} else {
|
||||
stats_.total_responses++;
|
||||
Duration response_latency = cluster_wide_time_microseconds_ - dop.requested_at;
|
||||
auto type_info = opaque_message.type_info;
|
||||
dop.promise.Fill(std::move(opaque_message), response_latency);
|
||||
histograms_.Measure(type_info, response_latency);
|
||||
dop.promise.Fill(std::move(opaque_message));
|
||||
}
|
||||
} else if (should_drop) {
|
||||
// don't add it anywhere, let it drop
|
||||
} else {
|
||||
// add to can_receive_ if not
|
||||
const auto &[om_vec, inserted] =
|
||||
can_receive_.try_emplace(to_address.ToPartialAddress(), std::vector<OpaqueMessage>());
|
||||
const auto &[om_vec, inserted] = can_receive_.try_emplace(to_address, std::vector<OpaqueMessage>());
|
||||
om_vec->second.emplace_back(std::move(opaque_message));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "io/address.hpp"
|
||||
#include "io/errors.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/message_histogram_collector.hpp"
|
||||
#include "io/simulator/simulator_config.hpp"
|
||||
#include "io/simulator/simulator_stats.hpp"
|
||||
#include "io/time.hpp"
|
||||
@@ -44,7 +43,7 @@ class SimulatorHandle {
|
||||
std::map<PromiseKey, DeadlineAndOpaquePromise> promises_;
|
||||
|
||||
// messages that are sent to servers that may later receive them
|
||||
std::map<PartialAddress, std::vector<OpaqueMessage>> can_receive_;
|
||||
std::map<Address, std::vector<OpaqueMessage>> can_receive_;
|
||||
|
||||
Time cluster_wide_time_microseconds_;
|
||||
bool should_shut_down_ = false;
|
||||
@@ -52,18 +51,15 @@ class SimulatorHandle {
|
||||
std::set<Address> blocked_on_receive_;
|
||||
std::set<Address> server_addresses_;
|
||||
std::mt19937 rng_;
|
||||
std::uniform_int_distribution<int> time_distrib_{5, 50};
|
||||
std::uniform_int_distribution<int> drop_distrib_{0, 99};
|
||||
SimulatorConfig config_;
|
||||
MessageHistogramCollector histograms_;
|
||||
RequestId request_id_counter_{0};
|
||||
|
||||
void TimeoutPromisesPastDeadline() {
|
||||
const Time now = cluster_wide_time_microseconds_;
|
||||
for (auto it = promises_.begin(); it != promises_.end();) {
|
||||
auto &[promise_key, dop] = *it;
|
||||
if (dop.deadline < now && config_.perform_timeouts) {
|
||||
spdlog::info("timing out request from requester {}.", promise_key.requester_address.ToString());
|
||||
if (dop.deadline < now) {
|
||||
spdlog::info("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(),
|
||||
promise_key.replier_address.ToString());
|
||||
std::move(dop).promise.TimeOut();
|
||||
it = promises_.erase(it);
|
||||
|
||||
@@ -78,16 +74,6 @@ class SimulatorHandle {
|
||||
explicit SimulatorHandle(SimulatorConfig config)
|
||||
: cluster_wide_time_microseconds_(config.start_time), rng_(config.rng_seed), config_(config) {}
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies();
|
||||
|
||||
~SimulatorHandle() {
|
||||
for (auto it = promises_.begin(); it != promises_.end();) {
|
||||
auto &[promise_key, dop] = *it;
|
||||
std::move(dop).promise.TimeOut();
|
||||
it = promises_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void IncrementServerCountAndWaitForQuiescentState(Address address);
|
||||
|
||||
/// This method causes most of the interesting simulation logic to happen, wrt network behavior.
|
||||
@@ -101,45 +87,28 @@ class SimulatorHandle {
|
||||
bool ShouldShutDown() const;
|
||||
|
||||
template <Message Request, Message Response>
|
||||
ResponseFuture<Response> SubmitRequest(Address to_address, Address from_address, Request &&request, Duration timeout,
|
||||
std::function<bool()> &&maybe_tick_simulator) {
|
||||
auto type_info = TypeInfoFor(request);
|
||||
|
||||
auto [future, promise] = memgraph::io::FuturePromisePairWithNotifier<ResponseResult<Response>>(
|
||||
std::forward<std::function<bool()>>(maybe_tick_simulator));
|
||||
|
||||
void SubmitRequest(Address to_address, Address from_address, RequestId request_id, Request &&request,
|
||||
Duration timeout, ResponsePromise<Response> &&promise) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
RequestId request_id = ++request_id_counter_;
|
||||
|
||||
const Time deadline = cluster_wide_time_microseconds_ + timeout;
|
||||
|
||||
std::any message(request);
|
||||
OpaqueMessage om{.to_address = to_address,
|
||||
.from_address = from_address,
|
||||
.request_id = request_id,
|
||||
.message = std::move(message),
|
||||
.type_info = type_info};
|
||||
.message = std::move(message)};
|
||||
in_flight_.emplace_back(std::make_pair(to_address, std::move(om)));
|
||||
|
||||
PromiseKey promise_key{.requester_address = from_address, .request_id = request_id};
|
||||
PromiseKey promise_key{.requester_address = from_address, .request_id = request_id, .replier_address = to_address};
|
||||
OpaquePromise opaque_promise(std::move(promise).ToUnique());
|
||||
DeadlineAndOpaquePromise dop{
|
||||
.requested_at = cluster_wide_time_microseconds_,
|
||||
.deadline = deadline,
|
||||
.promise = std::move(opaque_promise),
|
||||
};
|
||||
|
||||
MG_ASSERT(!promises_.contains(promise_key));
|
||||
|
||||
DeadlineAndOpaquePromise dop{.deadline = deadline, .promise = std::move(opaque_promise)};
|
||||
promises_.emplace(std::move(promise_key), std::move(dop));
|
||||
|
||||
stats_.total_messages++;
|
||||
stats_.total_requests++;
|
||||
|
||||
cv_.notify_all();
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
@@ -150,11 +119,9 @@ class SimulatorHandle {
|
||||
|
||||
const Time deadline = cluster_wide_time_microseconds_ + timeout;
|
||||
|
||||
auto partial_address = receiver.ToPartialAddress();
|
||||
|
||||
while (!should_shut_down_ && (cluster_wide_time_microseconds_ < deadline)) {
|
||||
if (can_receive_.contains(partial_address)) {
|
||||
std::vector<OpaqueMessage> &can_rx = can_receive_.at(partial_address);
|
||||
if (can_receive_.contains(receiver)) {
|
||||
std::vector<OpaqueMessage> &can_rx = can_receive_.at(receiver);
|
||||
if (!can_rx.empty()) {
|
||||
OpaqueMessage message = std::move(can_rx.back());
|
||||
can_rx.pop_back();
|
||||
@@ -162,7 +129,6 @@ class SimulatorHandle {
|
||||
// TODO(tyler) search for item in can_receive_ that matches the desired types, rather
|
||||
// than asserting that the last item in can_rx matches.
|
||||
auto m_opt = std::move(message).Take<Ms...>();
|
||||
MG_ASSERT(m_opt.has_value(), "Wrong message type received compared to the expected type");
|
||||
|
||||
blocked_on_receive_.erase(receiver);
|
||||
|
||||
@@ -185,14 +151,12 @@ class SimulatorHandle {
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M message) {
|
||||
auto type_info = TypeInfoFor(message);
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
std::any message_any(std::move(message));
|
||||
OpaqueMessage om{.to_address = to_address,
|
||||
.from_address = from_address,
|
||||
.request_id = request_id,
|
||||
.message = std::move(message_any),
|
||||
.type_info = type_info};
|
||||
.message = std::move(message_any)};
|
||||
in_flight_.emplace_back(std::make_pair(std::move(to_address), std::move(om)));
|
||||
|
||||
stats_.total_messages++;
|
||||
|
||||
@@ -33,11 +33,16 @@ class SimulatorTransport {
|
||||
: simulator_handle_(simulator_handle), address_(address), rng_(std::mt19937{seed}) {}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestT request, Duration timeout) {
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, uint64_t request_id, RequestT request,
|
||||
Duration timeout) {
|
||||
std::function<bool()> maybe_tick_simulator = [this] { return simulator_handle_->MaybeTickSimulator(); };
|
||||
auto [future, promise] =
|
||||
memgraph::io::FuturePromisePairWithNotifier<ResponseResult<ResponseT>>(maybe_tick_simulator);
|
||||
|
||||
return simulator_handle_->template SubmitRequest<RequestT, ResponseT>(to_address, from_address, std::move(request),
|
||||
timeout, std::move(maybe_tick_simulator));
|
||||
simulator_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
|
||||
std::move(promise));
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
@@ -58,7 +63,5 @@ class SimulatorTransport {
|
||||
Return Rand(D distrib) {
|
||||
return distrib(rng_);
|
||||
}
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies() { return simulator_handle_->ResponseLatencies(); }
|
||||
};
|
||||
}; // namespace memgraph::io::simulator
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "io/address.hpp"
|
||||
#include "io/errors.hpp"
|
||||
#include "io/future.hpp"
|
||||
#include "io/message_histogram_collector.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
@@ -41,7 +40,6 @@ struct ResponseEnvelope {
|
||||
RequestId request_id;
|
||||
Address to_address;
|
||||
Address from_address;
|
||||
Duration response_latency;
|
||||
};
|
||||
|
||||
template <Message M>
|
||||
@@ -68,6 +66,7 @@ template <typename I>
|
||||
class Io {
|
||||
I implementation_;
|
||||
Address address_;
|
||||
RequestId request_id_counter_ = 0;
|
||||
Duration default_timeout_ = std::chrono::microseconds{100000};
|
||||
|
||||
public:
|
||||
@@ -83,17 +82,20 @@ class Io {
|
||||
/// Issue a request with an explicit timeout in microseconds provided. This tends to be used by clients.
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> RequestWithTimeout(Address address, RequestT request, Duration timeout) {
|
||||
const RequestId request_id = ++request_id_counter_;
|
||||
const Address from_address = address_;
|
||||
return implementation_.template Request<RequestT, ResponseT>(address, from_address, request, timeout);
|
||||
return implementation_.template Request<RequestT, ResponseT>(address, from_address, request_id, request, timeout);
|
||||
}
|
||||
|
||||
/// Issue a request that times out after the default timeout. This tends
|
||||
/// to be used by clients.
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> Request(Address to_address, RequestT request) {
|
||||
const RequestId request_id = ++request_id_counter_;
|
||||
const Duration timeout = default_timeout_;
|
||||
const Address from_address = address_;
|
||||
return implementation_.template Request<RequestT, ResponseT>(to_address, from_address, std::move(request), timeout);
|
||||
return implementation_.template Request<RequestT, ResponseT>(to_address, from_address, request_id,
|
||||
std::move(request), timeout);
|
||||
}
|
||||
|
||||
/// Wait for an explicit number of microseconds for a request of one of the
|
||||
@@ -138,7 +140,5 @@ class Io {
|
||||
void SetAddress(Address address) { address_ = address; }
|
||||
|
||||
Io<I> ForkLocal() { return Io(implementation_, address_.ForkUniqueAddress()); }
|
||||
|
||||
LatencyHistogramSummaries ResponseLatencies() { return implementation_.ResponseLatencies(); }
|
||||
};
|
||||
}; // namespace memgraph::io
|
||||
|
||||
@@ -11,11 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
@@ -41,7 +37,6 @@ struct MachineConfig {
|
||||
bool is_query_engine;
|
||||
boost::asio::ip::address listen_ip;
|
||||
uint16_t listen_port;
|
||||
size_t shard_worker_threads = std::max(static_cast<unsigned int>(1), std::thread::hardware_concurrency());
|
||||
};
|
||||
|
||||
} // namespace memgraph::machine_manager
|
||||
|
||||
@@ -11,43 +11,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "coordinator/coordinator_rsm.hpp"
|
||||
#include "coordinator/coordinator_worker.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/messages.hpp"
|
||||
#include "io/rsm/rsm_client.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "machine_manager/machine_config.hpp"
|
||||
#include "storage/v3/shard_manager.hpp"
|
||||
#include <coordinator/coordinator_rsm.hpp>
|
||||
#include <io/message_conversion.hpp>
|
||||
#include <io/messages.hpp>
|
||||
#include <io/rsm/rsm_client.hpp>
|
||||
#include <io/time.hpp>
|
||||
#include <machine_manager/machine_config.hpp>
|
||||
#include <storage/v3/shard_manager.hpp>
|
||||
|
||||
namespace memgraph::machine_manager {
|
||||
|
||||
using coordinator::Coordinator;
|
||||
using coordinator::CoordinatorReadRequests;
|
||||
using coordinator::CoordinatorReadResponses;
|
||||
using coordinator::CoordinatorRsm;
|
||||
using coordinator::CoordinatorWriteRequests;
|
||||
using coordinator::CoordinatorWriteResponses;
|
||||
using coordinator::coordinator_worker::CoordinatorWorker;
|
||||
using CoordinatorRouteMessage = coordinator::coordinator_worker::RouteMessage;
|
||||
using CoordinatorQueue = coordinator::coordinator_worker::Queue;
|
||||
using io::ConvertVariant;
|
||||
using io::Duration;
|
||||
using io::RequestId;
|
||||
using io::Time;
|
||||
using io::messages::CoordinatorMessages;
|
||||
using io::messages::ShardManagerMessages;
|
||||
using io::messages::ShardMessages;
|
||||
using io::messages::StorageReadRequest;
|
||||
using io::messages::StorageWriteRequest;
|
||||
using io::rsm::AppendRequest;
|
||||
using io::rsm::AppendResponse;
|
||||
using io::rsm::ReadRequest;
|
||||
using io::rsm::VoteRequest;
|
||||
using io::rsm::VoteResponse;
|
||||
using io::rsm::WriteRequest;
|
||||
using io::rsm::WriteResponse;
|
||||
using storage::v3::ShardManager;
|
||||
using memgraph::coordinator::Coordinator;
|
||||
using memgraph::coordinator::CoordinatorReadRequests;
|
||||
using memgraph::coordinator::CoordinatorReadResponses;
|
||||
using memgraph::coordinator::CoordinatorRsm;
|
||||
using memgraph::coordinator::CoordinatorWriteRequests;
|
||||
using memgraph::coordinator::CoordinatorWriteResponses;
|
||||
using memgraph::io::ConvertVariant;
|
||||
using memgraph::io::Duration;
|
||||
using memgraph::io::RequestId;
|
||||
using memgraph::io::Time;
|
||||
using memgraph::io::messages::CoordinatorMessages;
|
||||
using memgraph::io::messages::ShardManagerMessages;
|
||||
using memgraph::io::messages::ShardMessages;
|
||||
using memgraph::io::messages::StorageReadRequest;
|
||||
using memgraph::io::messages::StorageWriteRequest;
|
||||
using memgraph::io::rsm::AppendRequest;
|
||||
using memgraph::io::rsm::AppendResponse;
|
||||
using memgraph::io::rsm::ReadRequest;
|
||||
using memgraph::io::rsm::VoteRequest;
|
||||
using memgraph::io::rsm::VoteResponse;
|
||||
using memgraph::io::rsm::WriteRequest;
|
||||
using memgraph::io::rsm::WriteResponse;
|
||||
using memgraph::storage::v3::ShardManager;
|
||||
|
||||
/// The MachineManager is responsible for:
|
||||
/// * starting the entire system and ensuring that high-level
|
||||
@@ -66,11 +62,9 @@ template <typename IoImpl>
|
||||
class MachineManager {
|
||||
io::Io<IoImpl> io_;
|
||||
MachineConfig config_;
|
||||
Address coordinator_address_;
|
||||
CoordinatorQueue coordinator_queue_;
|
||||
std::jthread coordinator_handle_;
|
||||
CoordinatorRsm<IoImpl> coordinator_;
|
||||
ShardManager<IoImpl> shard_manager_;
|
||||
Time next_cron_ = Time::min();
|
||||
Time next_cron_;
|
||||
|
||||
public:
|
||||
// TODO initialize ShardManager with "real" coordinator addresses instead of io.GetAddress
|
||||
@@ -78,27 +72,10 @@ class MachineManager {
|
||||
MachineManager(io::Io<IoImpl> io, MachineConfig config, Coordinator coordinator)
|
||||
: io_(io),
|
||||
config_(config),
|
||||
coordinator_address_(io.GetAddress().ForkUniqueAddress()),
|
||||
shard_manager_{io.ForkLocal(), config.shard_worker_threads, coordinator_address_} {
|
||||
auto coordinator_io = io.ForkLocal();
|
||||
coordinator_io.SetAddress(coordinator_address_);
|
||||
CoordinatorWorker coordinator_worker{coordinator_io, coordinator_queue_, coordinator};
|
||||
coordinator_handle_ = std::jthread([coordinator = std::move(coordinator_worker)]() mutable { coordinator.Run(); });
|
||||
}
|
||||
coordinator_{std::move(io.ForkLocal()), {}, std::move(coordinator)},
|
||||
shard_manager_(ShardManager{io.ForkLocal(), coordinator_.GetAddress()}) {}
|
||||
|
||||
MachineManager(MachineManager &&) noexcept = default;
|
||||
MachineManager &operator=(MachineManager &&) noexcept = default;
|
||||
MachineManager(const MachineManager &) = delete;
|
||||
MachineManager &operator=(const MachineManager &) = delete;
|
||||
|
||||
~MachineManager() {
|
||||
if (coordinator_handle_.joinable()) {
|
||||
coordinator_queue_.Push(coordinator::coordinator_worker::ShutDown{});
|
||||
coordinator_handle_.join();
|
||||
}
|
||||
}
|
||||
|
||||
Address CoordinatorAddress() { return coordinator_address_; }
|
||||
Address CoordinatorAddress() { return coordinator_.GetAddress(); }
|
||||
|
||||
void Run() {
|
||||
while (!io_.ShouldShutDown()) {
|
||||
@@ -108,7 +85,7 @@ class MachineManager {
|
||||
next_cron_ = Cron();
|
||||
}
|
||||
|
||||
Duration receive_timeout = std::max(next_cron_, now) - now;
|
||||
Duration receive_timeout = next_cron_ - now;
|
||||
|
||||
// Note: this parameter pack must be kept in-sync with the ReceiveWithTimeout parameter pack below
|
||||
using AllMessages =
|
||||
@@ -117,7 +94,7 @@ class MachineManager {
|
||||
WriteResponse<CoordinatorWriteResponses>, ReadRequest<StorageReadRequest>,
|
||||
AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>;
|
||||
|
||||
spdlog::info("MM waiting on Receive on address {}", io_.GetAddress().ToString());
|
||||
spdlog::info("MM waiting on Receive");
|
||||
|
||||
// Note: this parameter pack must be kept in-sync with the AllMessages parameter pack above
|
||||
auto request_result = io_.template ReceiveWithTimeout<
|
||||
@@ -128,6 +105,7 @@ class MachineManager {
|
||||
|
||||
if (request_result.HasError()) {
|
||||
// time to do Cron
|
||||
spdlog::info("MM got timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -136,7 +114,8 @@ class MachineManager {
|
||||
spdlog::info("MM got message to {}", request_envelope.to_address.ToString());
|
||||
|
||||
// If message is for the coordinator, cast it to subset and pass it to the coordinator
|
||||
bool to_coordinator = coordinator_address_ == request_envelope.to_address;
|
||||
bool to_coordinator = coordinator_.GetAddress() == request_envelope.to_address;
|
||||
spdlog::info("coordinator: {}", coordinator_.GetAddress().ToString());
|
||||
if (to_coordinator) {
|
||||
std::optional<CoordinatorMessages> conversion_attempt =
|
||||
ConvertVariant<AllMessages, ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>,
|
||||
@@ -149,13 +128,8 @@ class MachineManager {
|
||||
|
||||
CoordinatorMessages &&cm = std::move(conversion_attempt.value());
|
||||
|
||||
CoordinatorRouteMessage route_message{
|
||||
.message = std::move(cm),
|
||||
.request_id = request_envelope.request_id,
|
||||
.to = request_envelope.to_address,
|
||||
.from = request_envelope.from_address,
|
||||
};
|
||||
coordinator_queue_.Push(std::move(route_message));
|
||||
coordinator_.Handle(std::forward<CoordinatorMessages>(cm), request_envelope.request_id,
|
||||
request_envelope.from_address);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -182,7 +156,7 @@ class MachineManager {
|
||||
AppendResponse, WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>(
|
||||
std::move(request_envelope.message));
|
||||
|
||||
MG_ASSERT(conversion_attempt.has_value(), "shard rsm message conversion failed for {} - incorrect message type",
|
||||
MG_ASSERT(conversion_attempt.has_value(), "shard rsm message conversion failed for {}",
|
||||
request_envelope.to_address.ToString());
|
||||
|
||||
spdlog::info("got shard rsm message");
|
||||
@@ -196,7 +170,6 @@ class MachineManager {
|
||||
private:
|
||||
Time Cron() {
|
||||
spdlog::info("running MachineManager::Cron, address {}", io_.GetAddress().ToString());
|
||||
coordinator_queue_.Push(coordinator::coordinator_worker::Cron{});
|
||||
return shard_manager_.Cron();
|
||||
}
|
||||
};
|
||||
|
||||
817
src/memgraph.cpp
817
src/memgraph.cpp
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ set(mg_query_v2_sources
|
||||
${lcp_query_v2_cpp_files}
|
||||
common.cpp
|
||||
cypher_query_interpreter.cpp
|
||||
dump.cpp
|
||||
frontend/semantic/required_privileges.cpp
|
||||
frontend/stripped.cpp
|
||||
interpret/awesome_memgraph_functions.cpp
|
||||
@@ -22,7 +23,16 @@ set(mg_query_v2_sources
|
||||
plan/rewrite/index_lookup.cpp
|
||||
plan/rule_based_planner.cpp
|
||||
plan/variable_start_planner.cpp
|
||||
# procedure/mg_procedure_impl.cpp
|
||||
# procedure/mg_procedure_helpers.cpp
|
||||
# procedure/module.cpp
|
||||
# procedure/py_module.cpp
|
||||
serialization/property_value.cpp
|
||||
# stream/streams.cpp
|
||||
# stream/sources.cpp
|
||||
# stream/common.cpp
|
||||
# trigger.cpp
|
||||
# trigger_context.cpp
|
||||
bindings/typed_value.cpp
|
||||
accessors.cpp)
|
||||
|
||||
@@ -33,7 +43,7 @@ add_dependencies(mg-query-v2 generate_lcp_query_v2)
|
||||
target_include_directories(mg-query-v2 PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(mg-query-v2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
|
||||
target_link_libraries(mg-query-v2 dl cppitertools Boost::headers)
|
||||
target_link_libraries(mg-query-v2 mg-integrations-pulsar mg-integrations-kafka mg-storage-v3 mg-license mg-utils mg-kvstore mg-memory mg-coordinator)
|
||||
target_link_libraries(mg-query-v2 mg-integrations-pulsar mg-integrations-kafka mg-storage-v3 mg-license mg-utils mg-kvstore mg-memory)
|
||||
target_link_libraries(mg-query-v2 mg-expr)
|
||||
|
||||
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
||||
|
||||
@@ -11,63 +11,36 @@
|
||||
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
|
||||
namespace memgraph::query::v2::accessors {
|
||||
EdgeAccessor::EdgeAccessor(Edge edge, const msgs::ShardRequestManagerInterface *manager)
|
||||
: edge(std::move(edge)), manager_(manager) {}
|
||||
EdgeAccessor::EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props)
|
||||
: edge(std::move(edge)), properties(std::move(props)) {}
|
||||
|
||||
EdgeTypeId EdgeAccessor::EdgeType() const { return edge.type.id; }
|
||||
uint64_t EdgeAccessor::EdgeType() const { return edge.type.id; }
|
||||
|
||||
const std::vector<std::pair<PropertyId, Value>> &EdgeAccessor::Properties() const { return edge.properties; }
|
||||
|
||||
Value EdgeAccessor::GetProperty(const std::string &prop_name) const {
|
||||
auto prop_id = manager_->NameToProperty(prop_name);
|
||||
auto it = std::find_if(edge.properties.begin(), edge.properties.end(), [&](auto &pr) { return prop_id == pr.first; });
|
||||
if (it == edge.properties.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
std::vector<std::pair<PropertyId, Value>> EdgeAccessor::Properties() const {
|
||||
return properties;
|
||||
// std::map<std::string, TypedValue> res;
|
||||
// for (const auto &[name, value] : *properties) {
|
||||
// res[name] = ValueToTypedValue(value);
|
||||
// }
|
||||
// return res;
|
||||
}
|
||||
|
||||
const Edge &EdgeAccessor::GetEdge() const { return edge; }
|
||||
|
||||
bool EdgeAccessor::IsCycle() const { return edge.src == edge.dst; };
|
||||
|
||||
VertexAccessor EdgeAccessor::To() const {
|
||||
return VertexAccessor(Vertex{edge.dst}, std::vector<std::pair<PropertyId, msgs::Value>>{}, manager_);
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
Value EdgeAccessor::GetProperty(const std::string & /*prop_name*/) const {
|
||||
// TODO(kostasrim) fix this
|
||||
return {};
|
||||
}
|
||||
|
||||
VertexAccessor EdgeAccessor::From() const {
|
||||
return VertexAccessor(Vertex{edge.src}, std::vector<std::pair<PropertyId, msgs::Value>>{}, manager_);
|
||||
}
|
||||
Edge EdgeAccessor::GetEdge() const { return edge; }
|
||||
|
||||
VertexAccessor::VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props,
|
||||
const msgs::ShardRequestManagerInterface *manager)
|
||||
: vertex(std::move(v)), properties(std::move(props)), manager_(manager) {}
|
||||
VertexAccessor EdgeAccessor::To() const { return VertexAccessor(Vertex{edge.dst}, {}); }
|
||||
|
||||
VertexAccessor::VertexAccessor(Vertex v, std::map<PropertyId, Value> &&props,
|
||||
const msgs::ShardRequestManagerInterface *manager)
|
||||
: vertex(std::move(v)), manager_(manager) {
|
||||
properties.reserve(props.size());
|
||||
for (auto &[id, value] : props) {
|
||||
properties.emplace_back(std::make_pair(id, std::move(value)));
|
||||
}
|
||||
}
|
||||
VertexAccessor EdgeAccessor::From() const { return VertexAccessor(Vertex{edge.src}, {}); }
|
||||
|
||||
VertexAccessor::VertexAccessor(Vertex v, const std::map<PropertyId, Value> &props,
|
||||
const msgs::ShardRequestManagerInterface *manager)
|
||||
: vertex(std::move(v)), manager_(manager) {
|
||||
properties.reserve(props.size());
|
||||
for (const auto &[id, value] : props) {
|
||||
properties.emplace_back(std::make_pair(id, value));
|
||||
}
|
||||
}
|
||||
|
||||
Label VertexAccessor::PrimaryLabel() const { return vertex.id.first; }
|
||||
|
||||
const msgs::VertexId &VertexAccessor::Id() const { return vertex.id; }
|
||||
VertexAccessor::VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props)
|
||||
: vertex(std::move(v)), properties(std::move(props)) {}
|
||||
|
||||
std::vector<Label> VertexAccessor::Labels() const { return vertex.labels; }
|
||||
|
||||
@@ -76,19 +49,25 @@ bool VertexAccessor::HasLabel(Label &label) const {
|
||||
[label](const auto &l) { return l.id == label.id; }) != vertex.labels.end();
|
||||
}
|
||||
|
||||
const std::vector<std::pair<PropertyId, Value>> &VertexAccessor::Properties() const { return properties; }
|
||||
std::vector<std::pair<PropertyId, Value>> VertexAccessor::Properties() const {
|
||||
// std::map<std::string, TypedValue> res;
|
||||
// for (const auto &[name, value] : *properties) {
|
||||
// res[name] = ValueToTypedValue(value);
|
||||
// }
|
||||
// return res;
|
||||
return properties;
|
||||
}
|
||||
|
||||
Value VertexAccessor::GetProperty(PropertyId prop_id) const {
|
||||
auto it = std::find_if(properties.begin(), properties.end(), [&](auto &pr) { return prop_id == pr.first; });
|
||||
if (it == properties.end()) {
|
||||
return {};
|
||||
}
|
||||
return it->second;
|
||||
return std::find_if(properties.begin(), properties.end(), [&](auto &pr) { return prop_id == pr.first; })->second;
|
||||
// return ValueToTypedValue(properties[prop_name]);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
Value VertexAccessor::GetProperty(const std::string &prop_name) const {
|
||||
return GetProperty(manager_->NameToProperty(prop_name));
|
||||
Value VertexAccessor::GetProperty(const std::string & /*prop_name*/) const {
|
||||
// TODO(kostasrim) Add string mapping
|
||||
return {};
|
||||
// return ValueToTypedValue(properties[prop_name]);
|
||||
}
|
||||
|
||||
msgs::Vertex VertexAccessor::GetVertex() const { return vertex; }
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -24,10 +23,6 @@
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace memgraph::msgs {
|
||||
class ShardRequestManagerInterface;
|
||||
} // namespace memgraph::msgs
|
||||
|
||||
namespace memgraph::query::v2::accessors {
|
||||
|
||||
using Value = memgraph::msgs::Value;
|
||||
@@ -35,72 +30,62 @@ using Edge = memgraph::msgs::Edge;
|
||||
using Vertex = memgraph::msgs::Vertex;
|
||||
using Label = memgraph::msgs::Label;
|
||||
using PropertyId = memgraph::msgs::PropertyId;
|
||||
using EdgeTypeId = memgraph::msgs::EdgeTypeId;
|
||||
|
||||
class VertexAccessor;
|
||||
|
||||
class EdgeAccessor final {
|
||||
public:
|
||||
explicit EdgeAccessor(Edge edge, const msgs::ShardRequestManagerInterface *manager);
|
||||
EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props);
|
||||
|
||||
[[nodiscard]] EdgeTypeId EdgeType() const;
|
||||
uint64_t EdgeType() const;
|
||||
|
||||
[[nodiscard]] const std::vector<std::pair<PropertyId, Value>> &Properties() const;
|
||||
std::vector<std::pair<PropertyId, Value>> Properties() const;
|
||||
|
||||
[[nodiscard]] Value GetProperty(const std::string &prop_name) const;
|
||||
Value GetProperty(const std::string &prop_name) const;
|
||||
|
||||
[[nodiscard]] const Edge &GetEdge() const;
|
||||
|
||||
[[nodiscard]] bool IsCycle() const;
|
||||
Edge GetEdge() const;
|
||||
|
||||
// Dummy function
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] size_t CypherId() const { return 10; }
|
||||
inline size_t CypherId() const { return 10; }
|
||||
|
||||
// bool HasSrcAccessor const { return src == nullptr; }
|
||||
// bool HasDstAccessor const { return dst == nullptr; }
|
||||
|
||||
[[nodiscard]] VertexAccessor To() const;
|
||||
[[nodiscard]] VertexAccessor From() const;
|
||||
VertexAccessor To() const;
|
||||
VertexAccessor From() const;
|
||||
|
||||
friend bool operator==(const EdgeAccessor &lhs, const EdgeAccessor &rhs) { return lhs.edge == rhs.edge; }
|
||||
friend bool operator==(const EdgeAccessor &lhs, const EdgeAccessor &rhs) {
|
||||
return lhs.edge == rhs.edge && lhs.properties == rhs.properties;
|
||||
}
|
||||
|
||||
friend bool operator!=(const EdgeAccessor &lhs, const EdgeAccessor &rhs) { return !(lhs == rhs); }
|
||||
|
||||
private:
|
||||
Edge edge;
|
||||
const msgs::ShardRequestManagerInterface *manager_;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
class VertexAccessor final {
|
||||
public:
|
||||
using PropertyId = msgs::PropertyId;
|
||||
using Label = msgs::Label;
|
||||
using VertexId = msgs::VertexId;
|
||||
VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props,
|
||||
const msgs::ShardRequestManagerInterface *manager);
|
||||
VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props);
|
||||
|
||||
VertexAccessor(Vertex v, std::map<PropertyId, Value> &&props, const msgs::ShardRequestManagerInterface *manager);
|
||||
VertexAccessor(Vertex v, const std::map<PropertyId, Value> &props, const msgs::ShardRequestManagerInterface *manager);
|
||||
std::vector<Label> Labels() const;
|
||||
|
||||
[[nodiscard]] Label PrimaryLabel() const;
|
||||
bool HasLabel(Label &label) const;
|
||||
|
||||
[[nodiscard]] const msgs::VertexId &Id() const;
|
||||
std::vector<std::pair<PropertyId, Value>> Properties() const;
|
||||
|
||||
[[nodiscard]] std::vector<Label> Labels() const;
|
||||
Value GetProperty(PropertyId prop_id) const;
|
||||
Value GetProperty(const std::string &prop_name) const;
|
||||
|
||||
[[nodiscard]] bool HasLabel(Label &label) const;
|
||||
|
||||
[[nodiscard]] const std::vector<std::pair<PropertyId, Value>> &Properties() const;
|
||||
|
||||
[[nodiscard]] Value GetProperty(PropertyId prop_id) const;
|
||||
[[nodiscard]] Value GetProperty(const std::string &prop_name) const;
|
||||
|
||||
[[nodiscard]] msgs::Vertex GetVertex() const;
|
||||
msgs::Vertex GetVertex() const;
|
||||
|
||||
// Dummy function
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] size_t CypherId() const { return 10; }
|
||||
inline size_t CypherId() const { return 10; }
|
||||
|
||||
// auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
||||
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
||||
@@ -150,7 +135,6 @@ class VertexAccessor final {
|
||||
private:
|
||||
Vertex vertex;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
const msgs::ShardRequestManagerInterface *manager_;
|
||||
};
|
||||
|
||||
// inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
|
||||
|
||||
@@ -11,9 +11,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MG_AST_INCLUDE_PATH
|
||||
#error You are probably trying to include files from expr from both the storage and query engines! You will have a rough time kid!
|
||||
#endif
|
||||
|
||||
#define MG_AST_INCLUDE_PATH "query/v2/frontend/ast/ast.hpp" // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#define MG_INJECTED_NAMESPACE_NAME memgraph::query::v2 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
|
||||
@@ -23,10 +23,6 @@
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
namespace memgraph::msgs {
|
||||
class ShardRequestManagerInterface;
|
||||
} // namespace memgraph::msgs
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
inline const auto lam = [](const auto &val) { return ValueToTypedValue(val); };
|
||||
@@ -36,14 +32,13 @@ class Callable {
|
||||
auto operator()(const memgraph::storage::v3::PropertyValue &val) const {
|
||||
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val);
|
||||
};
|
||||
auto operator()(const msgs::Value &val, memgraph::msgs::ShardRequestManagerInterface *manager) const {
|
||||
return ValueToTypedValue(val, manager);
|
||||
};
|
||||
auto operator()(const msgs::Value &val) const { return ValueToTypedValue(val); };
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
using ExpressionEvaluator = memgraph::expr::ExpressionEvaluator<
|
||||
TypedValue, memgraph::query::v2::EvaluationContext, memgraph::msgs::ShardRequestManagerInterface, storage::v3::View,
|
||||
storage::v3::LabelId, msgs::Value, detail::Callable, memgraph::storage::v3::Error, memgraph::expr::QueryEngineTag>;
|
||||
using ExpressionEvaluator =
|
||||
memgraph::expr::ExpressionEvaluator<TypedValue, memgraph::query::v2::EvaluationContext, DbAccessor,
|
||||
storage::v3::View, storage::v3::LabelId, msgs::Value, detail::Callable,
|
||||
memgraph::storage::v3::Error, memgraph::expr::QueryEngineTag>;
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
@@ -13,15 +13,9 @@
|
||||
|
||||
#include "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/interpret/frame.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
#include "expr/interpret/frame.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
using Frame = memgraph::expr::Frame<TypedValue>;
|
||||
|
||||
struct MultiFrame {
|
||||
utils::pmr::vector<Frame> frames = utils::pmr::vector<Frame>(0, Frame{1}, utils::NewDeleteResource());
|
||||
size_t valid_frames{0};
|
||||
};
|
||||
} // namespace memgraph::query::v2
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/shard_operation_result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
@@ -93,13 +93,48 @@ concept AccessorWithSetPropertyAndValidate = requires(T accessor, const storage:
|
||||
const storage::v3::PropertyValue new_value) {
|
||||
{
|
||||
accessor.SetPropertyAndValidate(key, new_value)
|
||||
} -> std::same_as<storage::v3::ShardOperationResult<storage::v3::PropertyValue>>;
|
||||
} -> std::same_as<storage::v3::ResultSchema<storage::v3::PropertyValue>>;
|
||||
};
|
||||
|
||||
template <typename TRecordAccessor>
|
||||
concept RecordAccessor =
|
||||
AccessorWithSetProperty<TRecordAccessor> || AccessorWithSetPropertyAndValidate<TRecordAccessor>;
|
||||
|
||||
inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_violation, const DbAccessor &dba) {
|
||||
switch (schema_violation.status) {
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Primary key {} not defined on label :{}",
|
||||
storage::v3::SchemaTypeToString(schema_violation.violated_schema_property->type),
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Label :{} is not a primary label", dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Wrong type of property {} in schema :{}, should be of type {}",
|
||||
*schema_violation.violated_property_value, dba.LabelToName(schema_violation.label),
|
||||
storage::v3::SchemaTypeToString(schema_violation.violated_schema_property->type)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY: {
|
||||
throw SchemaViolationException(fmt::format("Updating of primary key {} on schema :{} not supported",
|
||||
*schema_violation.violated_property_value,
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL: {
|
||||
throw SchemaViolationException(fmt::format(
|
||||
"Adding primary label as secondary or removing primary label:", *schema_violation.violated_property_value,
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY: {
|
||||
throw SchemaViolationException(fmt::format("Cannot create vertex where primary label is secondary:{}",
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) {
|
||||
switch (error) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
@@ -110,11 +145,39 @@ inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) {
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
case storage::v3::Error::VERTEX_ALREADY_INSERTED:
|
||||
|
||||
throw QueryRuntimeException("Unexpected error when setting a property.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a property `value` mapped with given `key` on a `record`.
|
||||
///
|
||||
/// @throw QueryRuntimeException if value cannot be set as a property value
|
||||
template <RecordAccessor T>
|
||||
storage::v3::PropertyValue PropsSetChecked(T *record, const DbAccessor &dba, const storage::v3::PropertyId &key,
|
||||
const TypedValue &value) {
|
||||
try {
|
||||
if constexpr (std::is_same_v<T, VertexAccessor>) {
|
||||
const auto maybe_old_value = record->SetPropertyAndValidate(key, storage::v3::TypedToPropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); },
|
||||
[&dba](const storage::v3::SchemaViolation &schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, dba);
|
||||
}},
|
||||
maybe_old_value.GetError());
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
} else {
|
||||
// No validation on edge properties
|
||||
const auto maybe_old_value = record->SetProperty(key, storage::v3::TypedToPropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
HandleErrorOnPropertyUpdate(maybe_old_value.GetError());
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
}
|
||||
} catch (const expr::TypedValueException &) {
|
||||
throw QueryRuntimeException("'{}' cannot be used as a property value.", value.type());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t QueryTimestamp();
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "io/local_transport/local_transport.hpp"
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/metadata.hpp"
|
||||
@@ -25,23 +24,6 @@
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
// Used to store range of ids that are available
|
||||
// Used for edge id assignment
|
||||
class IdAllocator {
|
||||
public:
|
||||
IdAllocator() = default;
|
||||
IdAllocator(uint64_t low, uint64_t high) : current_edge_id_{low}, max_id_{high} {};
|
||||
|
||||
uint64_t AllocateId() {
|
||||
MG_ASSERT(current_edge_id_ < max_id_, "Current Edge Id went above max id");
|
||||
return current_edge_id_++;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t current_edge_id_;
|
||||
uint64_t max_id_;
|
||||
};
|
||||
|
||||
struct EvaluationContext {
|
||||
/// Memory for allocations during evaluation of a *single* Pull call.
|
||||
///
|
||||
@@ -60,28 +42,21 @@ struct EvaluationContext {
|
||||
mutable std::unordered_map<std::string, int64_t> counters;
|
||||
};
|
||||
|
||||
inline std::vector<storage::v3::PropertyId> NamesToProperties(
|
||||
const std::vector<std::string> &property_names, msgs::ShardRequestManagerInterface *shard_request_manager) {
|
||||
inline std::vector<storage::v3::PropertyId> NamesToProperties(const std::vector<std::string> &property_names,
|
||||
DbAccessor *dba) {
|
||||
std::vector<storage::v3::PropertyId> properties;
|
||||
// TODO Fix by using reference
|
||||
properties.reserve(property_names.size());
|
||||
if (shard_request_manager != nullptr) {
|
||||
for (const auto &name : property_names) {
|
||||
properties.push_back(shard_request_manager->NameToProperty(name));
|
||||
}
|
||||
for (const auto &name : property_names) {
|
||||
properties.push_back(dba->NameToProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
inline std::vector<storage::v3::LabelId> NamesToLabels(const std::vector<std::string> &label_names,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager) {
|
||||
inline std::vector<storage::v3::LabelId> NamesToLabels(const std::vector<std::string> &label_names, DbAccessor *dba) {
|
||||
std::vector<storage::v3::LabelId> labels;
|
||||
labels.reserve(label_names.size());
|
||||
// TODO Fix by using reference
|
||||
if (shard_request_manager != nullptr) {
|
||||
for (const auto &name : label_names) {
|
||||
labels.push_back(shard_request_manager->NameToLabel(name));
|
||||
}
|
||||
for (const auto &name : label_names) {
|
||||
labels.push_back(dba->NameToLabel(name));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
@@ -96,9 +71,9 @@ struct ExecutionContext {
|
||||
plan::ProfilingStats stats;
|
||||
plan::ProfilingStats *stats_root{nullptr};
|
||||
ExecutionStats execution_stats;
|
||||
// TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager{nullptr};
|
||||
IdAllocator *edge_ids_alloc;
|
||||
std::unique_ptr<msgs::ShardRequestManagerInterface> shard_request_manager{nullptr};
|
||||
};
|
||||
|
||||
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");
|
||||
|
||||
@@ -13,11 +13,10 @@
|
||||
#include "bindings/typed_value.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
inline TypedValue ValueToTypedValue(const msgs::Value &value, msgs::ShardRequestManagerInterface *manager) {
|
||||
inline TypedValue ValueToTypedValue(const msgs::Value &value) {
|
||||
using Value = msgs::Value;
|
||||
switch (value.type) {
|
||||
case Value::Type::Null:
|
||||
@@ -35,7 +34,7 @@ inline TypedValue ValueToTypedValue(const msgs::Value &value, msgs::ShardRequest
|
||||
std::vector<TypedValue> dst;
|
||||
dst.reserve(lst.size());
|
||||
for (const auto &elem : lst) {
|
||||
dst.push_back(ValueToTypedValue(elem, manager));
|
||||
dst.push_back(ValueToTypedValue(elem));
|
||||
}
|
||||
return TypedValue(std::move(dst));
|
||||
}
|
||||
@@ -43,15 +42,16 @@ inline TypedValue ValueToTypedValue(const msgs::Value &value, msgs::ShardRequest
|
||||
const auto &value_map = value.map_v;
|
||||
std::map<std::string, TypedValue> dst;
|
||||
for (const auto &[key, val] : value_map) {
|
||||
dst[key] = ValueToTypedValue(val, manager);
|
||||
dst[key] = ValueToTypedValue(val);
|
||||
}
|
||||
return TypedValue(std::move(dst));
|
||||
}
|
||||
case Value::Type::Vertex:
|
||||
return TypedValue(accessors::VertexAccessor(
|
||||
value.vertex_v, std::vector<std::pair<storage::v3::PropertyId, msgs::Value>>{}, manager));
|
||||
return TypedValue(accessors::VertexAccessor(value.vertex_v, {}));
|
||||
case Value::Type::Edge:
|
||||
return TypedValue(accessors::EdgeAccessor(value.edge_v, manager));
|
||||
return TypedValue(accessors::EdgeAccessor(value.edge_v, {}));
|
||||
case Value::Type::Path:
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Incorrect type in conversion");
|
||||
}
|
||||
@@ -91,10 +91,7 @@ inline msgs::Value TypedValueToValue(const TypedValue &value) {
|
||||
case TypedValue::Type::Edge:
|
||||
return Value(value.ValueEdge().GetEdge());
|
||||
case TypedValue::Type::Path:
|
||||
case TypedValue::Type::LocalTime:
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
case TypedValue::Type::Date:
|
||||
case TypedValue::Type::Duration:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Incorrect type in conversion");
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "query/v2/cypher_query_interpreter.hpp"
|
||||
#include "query/v2/bindings/symbol_generator.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
|
||||
@@ -118,9 +117,9 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery *query, const Parameters ¶meters,
|
||||
msgs::ShardRequestManagerInterface *shard_manager,
|
||||
DbAccessor *db_accessor,
|
||||
const std::vector<Identifier *> &predefined_identifiers) {
|
||||
auto vertex_counts = plan::MakeVertexCountCache(shard_manager);
|
||||
auto vertex_counts = plan::MakeVertexCountCache(db_accessor);
|
||||
auto symbol_table = expr::MakeSymbolTable(query, predefined_identifiers);
|
||||
auto planning_context = plan::MakePlanningContext(&ast_storage, &symbol_table, query, &vertex_counts);
|
||||
auto [root, cost] = plan::MakeLogicalPlan(&planning_context, parameters, FLAGS_query_cost_planner);
|
||||
@@ -130,7 +129,7 @@ std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery
|
||||
|
||||
std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
|
||||
const Parameters ¶meters, utils::SkipList<PlanCacheEntry> *plan_cache,
|
||||
msgs::ShardRequestManagerInterface *shard_manager,
|
||||
DbAccessor *db_accessor,
|
||||
const std::vector<Identifier *> &predefined_identifiers) {
|
||||
std::optional<utils::SkipList<PlanCacheEntry>::Accessor> plan_cache_access;
|
||||
if (plan_cache) {
|
||||
@@ -146,7 +145,7 @@ std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_stor
|
||||
}
|
||||
|
||||
auto plan = std::make_shared<CachedPlan>(
|
||||
MakeLogicalPlan(std::move(ast_storage), query, parameters, shard_manager, predefined_identifiers));
|
||||
MakeLogicalPlan(std::move(ast_storage), query, parameters, db_accessor, predefined_identifiers));
|
||||
if (plan_cache_access) {
|
||||
plan_cache_access->insert({hash, plan});
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class SingleNodeLogicalPlan final : public LogicalPlan {
|
||||
};
|
||||
|
||||
std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery *query, const Parameters ¶meters,
|
||||
msgs::ShardRequestManagerInterface *shard_manager,
|
||||
DbAccessor *db_accessor,
|
||||
const std::vector<Identifier *> &predefined_identifiers);
|
||||
|
||||
/**
|
||||
@@ -145,7 +145,7 @@ std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery
|
||||
*/
|
||||
std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
|
||||
const Parameters ¶meters, utils::SkipList<PlanCacheEntry> *plan_cache,
|
||||
msgs::ShardRequestManagerInterface *shard_manager,
|
||||
DbAccessor *db_accessor,
|
||||
const std::vector<Identifier *> &predefined_identifiers = {});
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/shard_operation_result.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Our communication layer and query engine don't mix
|
||||
@@ -114,19 +113,17 @@ class VertexAccessor final {
|
||||
|
||||
auto PrimaryKey(storage::v3::View view) const { return impl_.PrimaryKey(view); }
|
||||
|
||||
storage::v3::ShardOperationResult<bool> AddLabel(storage::v3::LabelId label) {
|
||||
storage::v3::ResultSchema<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabelAndValidate(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.AddLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<bool> AddLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.AddLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<bool> RemoveLabel(storage::v3::LabelId label) {
|
||||
storage::v3::ResultSchema<bool> RemoveLabel(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<bool> RemoveLabelAndValidate(storage::v3::LabelId label) {
|
||||
storage::v3::ResultSchema<bool> RemoveLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
}
|
||||
|
||||
@@ -141,17 +138,17 @@ class VertexAccessor final {
|
||||
return impl_.GetProperty(key, view);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetPropertyAndValidate(key, value);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<storage::v3::PropertyValue> SetPropertyAndValidate(
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetPropertyAndValidate(
|
||||
storage::v3::PropertyId key, const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetPropertyAndValidate(key, value);
|
||||
}
|
||||
|
||||
storage::v3::ShardOperationResult<storage::v3::PropertyValue> RemovePropertyAndValidate(storage::v3::PropertyId key) {
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> RemovePropertyAndValidate(storage::v3::PropertyId key) {
|
||||
return SetPropertyAndValidate(key, storage::v3::PropertyValue{});
|
||||
}
|
||||
|
||||
@@ -222,7 +219,195 @@ inline VertexAccessor EdgeAccessor::From() const { return *static_cast<VertexAcc
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class DbAccessor final {
|
||||
storage::v3::Shard::Accessor *accessor_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::v3::VerticesIterable iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::v3::VerticesIterable::Iterator it_;
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::v3::VerticesIterable::Iterator it) : it_(it) {}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::v3::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
|
||||
Iterator begin() { return Iterator(iterable_.begin()); }
|
||||
|
||||
Iterator end() { return Iterator(iterable_.end()); }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
std::optional<VertexAccessor> FindVertex(uint64_t /*unused*/) { return std::nullopt; }
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(primary_key, view);
|
||||
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
|
||||
return VerticesIterable(accessor_->Vertices(label, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, value, view));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
|
||||
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
|
||||
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
|
||||
auto maybe_vertex_acc = accessor_->CreateVertexAndValidate(primary_label, labels, properties);
|
||||
if (maybe_vertex_acc.HasError()) {
|
||||
return {std::move(maybe_vertex_acc.GetError())};
|
||||
}
|
||||
return VertexAccessor{maybe_vertex_acc.GetValue()};
|
||||
}
|
||||
|
||||
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::v3::EdgeTypeId &edge_type) {
|
||||
static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0);
|
||||
auto maybe_edge = accessor_->CreateEdge(from->impl_.Id(storage::v3::View::NEW).GetValue(),
|
||||
to->impl_.Id(storage::v3::View::NEW).GetValue(), edge_type, kDummyGid);
|
||||
if (maybe_edge.HasError()) return storage::v3::Result<EdgeAccessor>(maybe_edge.GetError());
|
||||
return EdgeAccessor(*maybe_edge);
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge) {
|
||||
auto res = accessor_->DeleteEdge(edge->impl_.FromVertex(), edge->impl_.ToVertex(), edge->impl_.Gid());
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<EdgeAccessor>{};
|
||||
}
|
||||
|
||||
return std::make_optional<EdgeAccessor>(*value);
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||
VertexAccessor *vertex_accessor) {
|
||||
using ReturnType = std::pair<VertexAccessor, std::vector<EdgeAccessor>>;
|
||||
|
||||
auto res = accessor_->DetachDeleteVertex(&vertex_accessor->impl_);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<ReturnType>{};
|
||||
}
|
||||
|
||||
const auto &[vertex, edges] = *value;
|
||||
|
||||
std::vector<EdgeAccessor> deleted_edges;
|
||||
deleted_edges.reserve(edges.size());
|
||||
std::transform(edges.begin(), edges.end(), std::back_inserter(deleted_edges),
|
||||
[](const auto &deleted_edge) { return EdgeAccessor{deleted_edge}; });
|
||||
|
||||
return std::make_optional<ReturnType>(vertex, std::move(deleted_edges));
|
||||
}
|
||||
|
||||
storage::v3::Result<std::optional<VertexAccessor>> RemoveVertex(VertexAccessor *vertex_accessor) {
|
||||
auto res = accessor_->DeleteVertex(&vertex_accessor->impl_);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
|
||||
const auto &value = res.GetValue();
|
||||
if (!value) {
|
||||
return std::optional<VertexAccessor>{};
|
||||
}
|
||||
|
||||
return {std::make_optional<VertexAccessor>(*value)};
|
||||
}
|
||||
|
||||
// TODO(jbajic) Query engine should have a map of labels, properties and edge
|
||||
// types
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }
|
||||
|
||||
const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); }
|
||||
|
||||
const std::string &LabelToName(storage::v3::LabelId label) const { return accessor_->LabelToName(label); }
|
||||
|
||||
const std::string &EdgeTypeToName(storage::v3::EdgeTypeId type) const { return accessor_->EdgeTypeToName(type); }
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
void Commit() { return accessor_->Commit(coordinator::Hlc{}); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
bool LabelIndexExists(storage::v3::LabelId label) const { return accessor_->LabelIndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(storage::v3::LabelId label, storage::v3::PropertyId prop) const {
|
||||
return accessor_->LabelPropertyIndexExists(label, prop);
|
||||
}
|
||||
|
||||
int64_t VerticesCount() const { return accessor_->ApproximateVertexCount(); }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label) const { return accessor_->ApproximateVertexCount(label); }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property) const {
|
||||
return accessor_->ApproximateVertexCount(label, property);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const storage::v3::PropertyValue &value) const {
|
||||
return accessor_->ApproximateVertexCount(label, property, value);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) const {
|
||||
return accessor_->ApproximateVertexCount(label, property, lower, upper);
|
||||
}
|
||||
|
||||
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
|
||||
|
||||
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
|
||||
|
||||
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
483
src/query/v2/dump.cpp
Normal file
483
src/query/v2/dump.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/dump.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/stream.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
namespace {
|
||||
|
||||
// Property that is used to make a difference among vertices. It is added to
|
||||
// property set of vertices to match edges and removed after the entire graph
|
||||
// is built.
|
||||
const char *kInternalPropertyId = "__mg_id__";
|
||||
|
||||
// Label that is attached to each vertex and is used for easier creation of
|
||||
// index on internal property id.
|
||||
const char *kInternalVertexLabel = "__mg_vertex__";
|
||||
|
||||
/// A helper function that escapes label, edge type and property names.
|
||||
std::string EscapeName(const std::string_view value) {
|
||||
std::string out;
|
||||
out.reserve(value.size() + 2);
|
||||
out.append(1, '`');
|
||||
for (auto c : value) {
|
||||
if (c == '`') {
|
||||
out.append("``");
|
||||
} else {
|
||||
out.append(1, c);
|
||||
}
|
||||
}
|
||||
out.append(1, '`');
|
||||
return out;
|
||||
}
|
||||
|
||||
void DumpPreciseDouble(std::ostream *os, double value) {
|
||||
// A temporary stream is used to keep precision of the original output
|
||||
// stream unchanged.
|
||||
std::ostringstream temp_oss;
|
||||
temp_oss << std::setprecision(std::numeric_limits<double>::max_digits10) << value;
|
||||
*os << temp_oss.str();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void DumpDate(std::ostream &os, const storage::v3::TemporalData &value) {
|
||||
utils::Date date(value.microseconds);
|
||||
os << "DATE(\"" << date << "\")";
|
||||
}
|
||||
|
||||
void DumpLocalTime(std::ostream &os, const storage::v3::TemporalData &value) {
|
||||
utils::LocalTime lt(value.microseconds);
|
||||
os << "LOCALTIME(\"" << lt << "\")";
|
||||
}
|
||||
|
||||
void DumpLocalDateTime(std::ostream &os, const storage::v3::TemporalData &value) {
|
||||
utils::LocalDateTime ldt(value.microseconds);
|
||||
os << "LOCALDATETIME(\"" << ldt << "\")";
|
||||
}
|
||||
|
||||
void DumpDuration(std::ostream &os, const storage::v3::TemporalData &value) {
|
||||
utils::Duration dur(value.microseconds);
|
||||
os << "DURATION(\"" << dur << "\")";
|
||||
}
|
||||
|
||||
void DumpTemporalData(std::ostream &os, const storage::v3::TemporalData &value) {
|
||||
switch (value.type) {
|
||||
case storage::v3::TemporalType::Date: {
|
||||
DumpDate(os, value);
|
||||
return;
|
||||
}
|
||||
case storage::v3::TemporalType::LocalTime: {
|
||||
DumpLocalTime(os, value);
|
||||
return;
|
||||
}
|
||||
case storage::v3::TemporalType::LocalDateTime: {
|
||||
DumpLocalDateTime(os, value);
|
||||
return;
|
||||
}
|
||||
case storage::v3::TemporalType::Duration: {
|
||||
DumpDuration(os, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DumpPropertyValue(std::ostream *os, const storage::v3::PropertyValue &value) {
|
||||
switch (value.type()) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
*os << "Null";
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Bool:
|
||||
*os << (value.ValueBool() ? "true" : "false");
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::String:
|
||||
*os << utils::Escape(value.ValueString());
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Int:
|
||||
*os << value.ValueInt();
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::Double:
|
||||
DumpPreciseDouble(os, value.ValueDouble());
|
||||
return;
|
||||
case storage::v3::PropertyValue::Type::List: {
|
||||
*os << "[";
|
||||
const auto &list = value.ValueList();
|
||||
utils::PrintIterable(*os, list, ", ", [](auto &os, const auto &item) { DumpPropertyValue(&os, item); });
|
||||
*os << "]";
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Map: {
|
||||
*os << "{";
|
||||
const auto &map = value.ValueMap();
|
||||
utils::PrintIterable(*os, map, ", ", [](auto &os, const auto &kv) {
|
||||
os << EscapeName(kv.first) << ": ";
|
||||
DumpPropertyValue(&os, kv.second);
|
||||
});
|
||||
*os << "}";
|
||||
return;
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::TemporalData: {
|
||||
DumpTemporalData(*os, value.ValueTemporalData());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DumpProperties(std::ostream *os, query::v2::DbAccessor *dba,
|
||||
const std::map<storage::v3::PropertyId, storage::v3::PropertyValue> &store,
|
||||
std::optional<int64_t> property_id = std::nullopt) {
|
||||
*os << "{";
|
||||
if (property_id) {
|
||||
*os << kInternalPropertyId << ": " << *property_id;
|
||||
if (store.size() > 0) *os << ", ";
|
||||
}
|
||||
utils::PrintIterable(*os, store, ", ", [&dba](auto &os, const auto &kv) {
|
||||
os << EscapeName(dba->PropertyToName(kv.first)) << ": ";
|
||||
DumpPropertyValue(&os, kv.second);
|
||||
});
|
||||
*os << "}";
|
||||
}
|
||||
|
||||
void DumpVertex(std::ostream *os, query::v2::DbAccessor *dba, const query::v2::VertexAccessor &vertex) {
|
||||
*os << "CREATE (";
|
||||
*os << ":" << kInternalVertexLabel;
|
||||
auto maybe_labels = vertex.Labels(storage::v3::View::OLD);
|
||||
if (maybe_labels.HasError()) {
|
||||
switch (maybe_labels.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get labels from a deleted node.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw query::v2::QueryRuntimeException("Unexpected error when getting labels.");
|
||||
}
|
||||
}
|
||||
for (const auto &label : *maybe_labels) {
|
||||
*os << ":" << EscapeName(dba->LabelToName(label));
|
||||
}
|
||||
*os << " ";
|
||||
auto maybe_props = vertex.Properties(storage::v3::View::OLD);
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get properties from a deleted object.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get properties from a node that doesn't exist.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw query::v2::QueryRuntimeException("Unexpected error when getting properties.");
|
||||
}
|
||||
}
|
||||
DumpProperties(os, dba, *maybe_props, vertex.CypherId());
|
||||
*os << ");";
|
||||
}
|
||||
|
||||
void DumpEdge(std::ostream *os, query::v2::DbAccessor *dba, const query::v2::EdgeAccessor &edge) {
|
||||
*os << "MATCH ";
|
||||
*os << "(u:" << kInternalVertexLabel << "), ";
|
||||
*os << "(v:" << kInternalVertexLabel << ")";
|
||||
*os << " WHERE ";
|
||||
*os << "u." << kInternalPropertyId << " = " << edge.From().CypherId();
|
||||
*os << " AND ";
|
||||
*os << "v." << kInternalPropertyId << " = " << edge.To().CypherId() << " ";
|
||||
*os << "CREATE (u)-[";
|
||||
*os << ":" << EscapeName(dba->EdgeTypeToName(edge.EdgeType()));
|
||||
auto maybe_props = edge.Properties(storage::v3::View::OLD);
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get properties from a deleted object.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get properties from an edge that doesn't exist.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw query::v2::QueryRuntimeException("Unexpected error when getting properties.");
|
||||
}
|
||||
}
|
||||
if (maybe_props->size() > 0) {
|
||||
*os << " ";
|
||||
DumpProperties(os, dba, *maybe_props);
|
||||
}
|
||||
*os << "]->(v);";
|
||||
}
|
||||
|
||||
void DumpLabelIndex(std::ostream *os, query::v2::DbAccessor *dba, const storage::v3::LabelId label) {
|
||||
*os << "CREATE INDEX ON :" << EscapeName(dba->LabelToName(label)) << ";";
|
||||
}
|
||||
|
||||
void DumpLabelPropertyIndex(std::ostream *os, query::v2::DbAccessor *dba, storage::v3::LabelId label,
|
||||
storage::v3::PropertyId property) {
|
||||
*os << "CREATE INDEX ON :" << EscapeName(dba->LabelToName(label)) << "(" << EscapeName(dba->PropertyToName(property))
|
||||
<< ");";
|
||||
}
|
||||
|
||||
void DumpExistenceConstraint(std::ostream *os, query::v2::DbAccessor *dba, storage::v3::LabelId label,
|
||||
storage::v3::PropertyId property) {
|
||||
*os << "CREATE CONSTRAINT ON (u:" << EscapeName(dba->LabelToName(label)) << ") ASSERT EXISTS (u."
|
||||
<< EscapeName(dba->PropertyToName(property)) << ");";
|
||||
}
|
||||
|
||||
void DumpUniqueConstraint(std::ostream *os, query::v2::DbAccessor *dba, storage::v3::LabelId label,
|
||||
const std::set<storage::v3::PropertyId> &properties) {
|
||||
*os << "CREATE CONSTRAINT ON (u:" << EscapeName(dba->LabelToName(label)) << ") ASSERT ";
|
||||
utils::PrintIterable(*os, properties, ", ", [&dba](auto &stream, const auto &property) {
|
||||
stream << "u." << EscapeName(dba->PropertyToName(property));
|
||||
});
|
||||
*os << " IS UNIQUE;";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PullPlanDump::PullPlanDump(DbAccessor *dba)
|
||||
: dba_(dba),
|
||||
vertices_iterable_(dba->Vertices(storage::v3::View::OLD)),
|
||||
pull_chunks_{// Dump all label indices
|
||||
CreateLabelIndicesPullChunk(),
|
||||
// Dump all label property indices
|
||||
CreateLabelPropertyIndicesPullChunk(),
|
||||
// Create internal index for faster edge creation
|
||||
CreateInternalIndexPullChunk(),
|
||||
// Dump all vertices
|
||||
CreateVertexPullChunk(),
|
||||
// Dump all edges
|
||||
CreateEdgePullChunk(),
|
||||
// Drop the internal index
|
||||
CreateDropInternalIndexPullChunk(),
|
||||
// Internal index cleanup
|
||||
CreateInternalIndexCleanupPullChunk()} {}
|
||||
|
||||
bool PullPlanDump::Pull(AnyStream *stream, std::optional<int> n) {
|
||||
// Iterate all functions that stream some results.
|
||||
// Each function should return number of results it streamed after it
|
||||
// finishes. If the function did not finish streaming all the results,
|
||||
// std::nullopt should be returned because n results have already been sent.
|
||||
while (current_chunk_index_ < pull_chunks_.size() && (!n || *n > 0)) {
|
||||
const auto maybe_streamed_count = pull_chunks_[current_chunk_index_](stream, n);
|
||||
|
||||
if (!maybe_streamed_count) {
|
||||
// n wasn't large enough to stream all the results from the current chunk
|
||||
break;
|
||||
}
|
||||
|
||||
if (n) {
|
||||
// chunk finished streaming its results
|
||||
// subtract number of results streamed in current pull
|
||||
// so we know how many results we need to stream from future
|
||||
// chunks.
|
||||
*n -= *maybe_streamed_count;
|
||||
}
|
||||
|
||||
++current_chunk_index_;
|
||||
}
|
||||
return current_chunk_index_ == pull_chunks_.size();
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateLabelIndicesPullChunk() {
|
||||
// Dump all label indices
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of indices vectors
|
||||
if (!indices_info_) {
|
||||
indices_info_.emplace(dba_->ListAllIndices());
|
||||
}
|
||||
const auto &label = indices_info_->label;
|
||||
|
||||
size_t local_counter = 0;
|
||||
while (global_index < label.size() && (!n || local_counter < *n)) {
|
||||
std::ostringstream os;
|
||||
DumpLabelIndex(&os, dba_, label[global_index]);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == label.size()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() {
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of indices vectors
|
||||
if (!indices_info_) {
|
||||
indices_info_.emplace(dba_->ListAllIndices());
|
||||
}
|
||||
const auto &label_property = indices_info_->label_property;
|
||||
|
||||
size_t local_counter = 0;
|
||||
while (global_index < label_property.size() && (!n || local_counter < *n)) {
|
||||
std::ostringstream os;
|
||||
const auto &label_property_index = label_property[global_index];
|
||||
DumpLabelPropertyIndex(&os, dba_, label_property_index.first, label_property_index.second);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == label_property.size()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexPullChunk() {
|
||||
return [this](AnyStream *stream, std::optional<int>) mutable -> std::optional<size_t> {
|
||||
if (vertices_iterable_.begin() != vertices_iterable_.end()) {
|
||||
std::ostringstream os;
|
||||
os << "CREATE INDEX ON :" << kInternalVertexLabel << "(" << kInternalPropertyId << ");";
|
||||
stream->Result({TypedValue(os.str())});
|
||||
internal_index_created_ = true;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateVertexPullChunk() {
|
||||
return [this, maybe_current_iter = std::optional<VertexAccessorIterableIterator>{}](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the call of begin() function
|
||||
// If multiple begins are called before an iteration,
|
||||
// one iteration will make the rest of iterators be in undefined
|
||||
// states.
|
||||
if (!maybe_current_iter) {
|
||||
maybe_current_iter.emplace(vertices_iterable_.begin());
|
||||
}
|
||||
|
||||
auto ¤t_iter{*maybe_current_iter};
|
||||
|
||||
size_t local_counter = 0;
|
||||
while (current_iter != vertices_iterable_.end() && (!n || local_counter < *n)) {
|
||||
std::ostringstream os;
|
||||
DumpVertex(&os, dba_, *current_iter);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
++local_counter;
|
||||
++current_iter;
|
||||
}
|
||||
if (current_iter == vertices_iterable_.end()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateEdgePullChunk() {
|
||||
return [this, maybe_current_vertex_iter = std::optional<VertexAccessorIterableIterator>{},
|
||||
// we need to save the iterable which contains list of accessor so
|
||||
// our saved iterator is valid in the next run
|
||||
maybe_edge_iterable = std::shared_ptr<EdgeAccessorIterable>{nullptr},
|
||||
maybe_current_edge_iter = std::optional<EdgeAccessorIterableIterator>{}](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the call of begin() function
|
||||
// If multiple begins are called before an iteration,
|
||||
// one iteration will make the rest of iterators be in undefined
|
||||
// states.
|
||||
if (!maybe_current_vertex_iter) {
|
||||
maybe_current_vertex_iter.emplace(vertices_iterable_.begin());
|
||||
}
|
||||
|
||||
auto ¤t_vertex_iter{*maybe_current_vertex_iter};
|
||||
size_t local_counter = 0U;
|
||||
for (; current_vertex_iter != vertices_iterable_.end() && (!n || local_counter < *n); ++current_vertex_iter) {
|
||||
const auto &vertex = *current_vertex_iter;
|
||||
// If we have a saved iterable from a previous pull
|
||||
// we need to use the same iterable
|
||||
if (!maybe_edge_iterable) {
|
||||
maybe_edge_iterable = std::make_shared<EdgeAccessorIterable>(vertex.OutEdges(storage::v3::View::OLD));
|
||||
}
|
||||
auto &maybe_edges = *maybe_edge_iterable;
|
||||
MG_ASSERT(maybe_edges.HasValue(), "Invalid database state!");
|
||||
auto current_edge_iter = maybe_current_edge_iter ? *maybe_current_edge_iter : maybe_edges->begin();
|
||||
for (; current_edge_iter != maybe_edges->end() && (!n || local_counter < *n); ++current_edge_iter) {
|
||||
std::ostringstream os;
|
||||
DumpEdge(&os, dba_, *current_edge_iter);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (current_edge_iter != maybe_edges->end()) {
|
||||
maybe_current_edge_iter.emplace(current_edge_iter);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
maybe_current_edge_iter = std::nullopt;
|
||||
maybe_edge_iterable = nullptr;
|
||||
}
|
||||
|
||||
if (current_vertex_iter == vertices_iterable_.end()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateDropInternalIndexPullChunk() {
|
||||
return [this](AnyStream *stream, std::optional<int>) {
|
||||
if (internal_index_created_) {
|
||||
std::ostringstream os;
|
||||
os << "DROP INDEX ON :" << kInternalVertexLabel << "(" << kInternalPropertyId << ");";
|
||||
stream->Result({TypedValue(os.str())});
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexCleanupPullChunk() {
|
||||
return [this](AnyStream *stream, std::optional<int>) {
|
||||
if (internal_index_created_) {
|
||||
std::ostringstream os;
|
||||
os << "MATCH (u) REMOVE u:" << kInternalVertexLabel << ", u." << kInternalPropertyId << ";";
|
||||
stream->Result({TypedValue(os.str())});
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
void DumpDatabaseToCypherQueries(query::v2::DbAccessor *dba, AnyStream *stream) { PullPlanDump(dba).Pull(stream, {}); }
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
63
src/query/v2/dump.hpp
Normal file
63
src/query/v2/dump.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 <ostream>
|
||||
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/stream.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
void DumpDatabaseToCypherQueries(query::v2::DbAccessor *dba, AnyStream *stream);
|
||||
|
||||
struct PullPlanDump {
|
||||
explicit PullPlanDump(query::v2::DbAccessor *dba);
|
||||
|
||||
/// Pull the dump results lazily
|
||||
/// @return true if all results were returned, false otherwise
|
||||
bool Pull(AnyStream *stream, std::optional<int> n);
|
||||
|
||||
private:
|
||||
query::v2::DbAccessor *dba_ = nullptr;
|
||||
|
||||
std::optional<storage::v3::IndicesInfo> indices_info_ = std::nullopt;
|
||||
|
||||
using VertexAccessorIterable = decltype(std::declval<query::v2::DbAccessor>().Vertices(storage::v3::View::OLD));
|
||||
using VertexAccessorIterableIterator = decltype(std::declval<VertexAccessorIterable>().begin());
|
||||
|
||||
using EdgeAccessorIterable = decltype(std::declval<VertexAccessor>().OutEdges(storage::v3::View::OLD));
|
||||
using EdgeAccessorIterableIterator = decltype(std::declval<EdgeAccessorIterable>().GetValue().begin());
|
||||
|
||||
VertexAccessorIterable vertices_iterable_;
|
||||
bool internal_index_created_ = false;
|
||||
|
||||
size_t current_chunk_index_ = 0;
|
||||
|
||||
using PullChunk = std::function<std::optional<size_t>(AnyStream *stream, std::optional<int> n)>;
|
||||
// We define every part of the dump query in a self contained function.
|
||||
// Each functions is responsible of keeping track of its execution status.
|
||||
// If a function did finish its execution, it should return number of results
|
||||
// it streamed so we know how many rows should be pulled from the next
|
||||
// function, otherwise std::nullopt is returned.
|
||||
std::vector<PullChunk> pull_chunks_;
|
||||
|
||||
PullChunk CreateLabelIndicesPullChunk();
|
||||
PullChunk CreateLabelPropertyIndicesPullChunk();
|
||||
PullChunk CreateInternalIndexPullChunk();
|
||||
PullChunk CreateVertexPullChunk();
|
||||
PullChunk CreateEdgePullChunk();
|
||||
PullChunk CreateDropInternalIndexPullChunk();
|
||||
PullChunk CreateInternalIndexCleanupPullChunk();
|
||||
};
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/bindings/ast_visitor.hpp"
|
||||
#include "common/types.hpp"
|
||||
#include "query/v2/bindings/symbol.hpp"
|
||||
#include "query/v2/interpret/awesome_memgraph_functions.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/conversions.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "query/v2/procedure/cypher_types.hpp"
|
||||
//#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
//#include "query/v2/procedure/module.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
@@ -412,6 +415,48 @@ TypedValue StartNode(const TypedValue *args, int64_t nargs, const FunctionContex
|
||||
return TypedValue(args[0].ValueEdge().From(), ctx.memory);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
size_t UnwrapDegreeResult(storage::v3::Result<size_t> maybe_degree) {
|
||||
if (maybe_degree.HasError()) {
|
||||
switch (maybe_degree.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to get degree of a deleted node.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get degree of a node that doesn't exist.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Unexpected error when getting node degree.");
|
||||
}
|
||||
}
|
||||
return *maybe_degree;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex>>("degree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
// TODO(kostasrim) Fix dummy values
|
||||
return TypedValue(int64_t(0), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue InDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex>>("inDegree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
return TypedValue(int64_t(0), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue OutDegree(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex>>("outDegree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
return TypedValue(int64_t(0), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Bool, Integer, String>>("toBoolean", args, nargs);
|
||||
const auto &value = args[0];
|
||||
@@ -473,8 +518,9 @@ TypedValue ToInteger(const TypedValue *args, int64_t nargs, const FunctionContex
|
||||
|
||||
TypedValue Type(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Edge>>("type", args, nargs);
|
||||
auto *dba = ctx.db_accessor;
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return TypedValue(args[0].ValueEdge().EdgeType().AsInt(), ctx.memory);
|
||||
return TypedValue(static_cast<int64_t>(args[0].ValueEdge().EdgeType()), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
@@ -513,6 +559,30 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: How is Keys different from Properties function?
|
||||
TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext & /*ctx*/) {
|
||||
FType<Or<Null, Vertex, Edge>>("keys", args, nargs);
|
||||
return {};
|
||||
}
|
||||
|
||||
TypedValue Labels(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex>>("labels", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return {};
|
||||
}
|
||||
|
||||
TypedValue Nodes(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Path>>("nodes", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return {};
|
||||
}
|
||||
|
||||
TypedValue Relationships(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Path>>("relationships", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return {};
|
||||
}
|
||||
|
||||
TypedValue Range(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Integer>, Or<Null, Integer>, Optional<Or<Null, NonZeroInteger>>>("range", args, nargs);
|
||||
for (int64_t i = 0; i < nargs; ++i)
|
||||
@@ -1028,6 +1098,9 @@ TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext
|
||||
std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction(
|
||||
const std::string &function_name) {
|
||||
// Scalar functions
|
||||
if (function_name == "DEGREE") return Degree;
|
||||
if (function_name == "INDEGREE") return InDegree;
|
||||
if (function_name == "OUTDEGREE") return OutDegree;
|
||||
if (function_name == "ENDNODE") return EndNode;
|
||||
if (function_name == "HEAD") return Head;
|
||||
if (function_name == kId) return Id;
|
||||
@@ -1043,7 +1116,11 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
|
||||
if (function_name == "VALUETYPE") return ValueType;
|
||||
|
||||
// List functions
|
||||
if (function_name == "KEYS") return Keys;
|
||||
if (function_name == "LABELS") return Labels;
|
||||
if (function_name == "NODES") return Nodes;
|
||||
if (function_name == "RANGE") return Range;
|
||||
if (function_name == "RELATIONSHIPS") return Relationships;
|
||||
if (function_name == "TAIL") return Tail;
|
||||
if (function_name == "UNIFORMSAMPLE") return UniformSample;
|
||||
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::msgs {
|
||||
class ShardRequestManagerInterface;
|
||||
} // namespace memgraph::msgs
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
class DbAccessor;
|
||||
|
||||
namespace {
|
||||
const char kStartsWith[] = "STARTSWITH";
|
||||
const char kEndsWith[] = "ENDSWITH";
|
||||
@@ -34,9 +31,7 @@ const char kId[] = "ID";
|
||||
} // namespace
|
||||
|
||||
struct FunctionContext {
|
||||
// TODO(kostasrim) consider optional here. ShardRequestManager does not exist on the storage.
|
||||
// DbAccessor *db_accessor;
|
||||
msgs::ShardRequestManagerInterface *manager;
|
||||
DbAccessor *db_accessor;
|
||||
utils::MemoryResource *memory;
|
||||
int64_t timestamp;
|
||||
std::unordered_map<std::string, int64_t> *counters;
|
||||
|
||||
@@ -19,13 +19,9 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "coordinator/coordinator_client.hpp"
|
||||
#include "expr/ast/ast_visitor.hpp"
|
||||
#include "io/local_transport/local_system.hpp"
|
||||
#include "io/local_transport/local_transport.hpp"
|
||||
#include "memory/memory_control.hpp"
|
||||
#include "parser/opencypher/parser.hpp"
|
||||
#include "query/v2/bindings/eval.hpp"
|
||||
@@ -37,6 +33,7 @@
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/cypher_query_interpreter.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/dump.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/semantic/required_privileges.hpp"
|
||||
@@ -44,7 +41,6 @@
|
||||
#include "query/v2/plan/planner.hpp"
|
||||
#include "query/v2/plan/profile.hpp"
|
||||
#include "query/v2/plan/vertex_count_cache.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
@@ -118,6 +114,17 @@ std::optional<TResult> GetOptionalValue(query::v2::Expression *expression, Expre
|
||||
return {};
|
||||
};
|
||||
|
||||
std::optional<std::string> GetOptionalStringValue(query::v2::Expression *expression, ExpressionEvaluator &evaluator) {
|
||||
if (expression != nullptr) {
|
||||
auto value = expression->Accept(evaluator);
|
||||
MG_ASSERT(value.IsNull() || value.IsString());
|
||||
if (value.IsString()) {
|
||||
return {std::string(value.ValueString().begin(), value.ValueString().end())};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
class ReplQueryHandler final : public query::v2::ReplicationQueryHandler {
|
||||
public:
|
||||
explicit ReplQueryHandler(storage::v3::Shard * /*db*/) {}
|
||||
@@ -144,18 +151,18 @@ class ReplQueryHandler final : public query::v2::ReplicationQueryHandler {
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
|
||||
Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Parameters ¶meters,
|
||||
msgs::ShardRequestManagerInterface *manager) {
|
||||
DbAccessor *db_accessor) {
|
||||
// Empty frame for evaluation of password expression. This is OK since
|
||||
// password should be either null or string literal and it's evaluation
|
||||
// should not depend on frame.
|
||||
Frame frame(0);
|
||||
expr::Frame<TypedValue> frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, manager, storage::v3::View::OLD);
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::v3::View::OLD);
|
||||
|
||||
std::string username = auth_query->user_;
|
||||
std::string rolename = auth_query->role_;
|
||||
@@ -313,16 +320,16 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
}
|
||||
|
||||
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters,
|
||||
InterpreterContext *interpreter_context, msgs::ShardRequestManagerInterface *manager,
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
|
||||
std::vector<Notification> *notifications) {
|
||||
Frame frame(0);
|
||||
expr::Frame<TypedValue> frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, manager, storage::v3::View::OLD);
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::v3::View::OLD);
|
||||
|
||||
Callback callback;
|
||||
switch (repl_query->action_) {
|
||||
@@ -448,9 +455,23 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶meters,
|
||||
msgs::ShardRequestManagerInterface *manager) {
|
||||
Frame frame(0);
|
||||
std::optional<std::string> StringPointerToOptional(const std::string *str) {
|
||||
return str == nullptr ? std::nullopt : std::make_optional(*str);
|
||||
}
|
||||
|
||||
std::vector<std::string> EvaluateTopicNames(ExpressionEvaluator &evaluator,
|
||||
std::variant<Expression *, std::vector<std::string>> topic_variant) {
|
||||
return std::visit(utils::Overloaded{[&](Expression *expression) {
|
||||
auto topic_names = expression->Accept(evaluator);
|
||||
MG_ASSERT(topic_names.IsString());
|
||||
return utils::Split(topic_names.ValueString(), ",");
|
||||
},
|
||||
[&](std::vector<std::string> topic_names) { return topic_names; }},
|
||||
std::move(topic_variant));
|
||||
}
|
||||
|
||||
Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶meters, DbAccessor *db_accessor) {
|
||||
expr::Frame<TypedValue> frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
@@ -459,7 +480,7 @@ Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶m
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, manager, storage::v3::View::OLD);
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::v3::View::OLD);
|
||||
|
||||
Callback callback;
|
||||
switch (setting_query->action_) {
|
||||
@@ -650,21 +671,16 @@ struct PullPlanVector {
|
||||
struct PullPlan {
|
||||
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, bool is_profile_query,
|
||||
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager = nullptr,
|
||||
// TriggerContextCollector *trigger_context_collector = nullptr,
|
||||
std::optional<size_t> memory_limit = {});
|
||||
std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n,
|
||||
const std::vector<Symbol> &output_symbols,
|
||||
std::map<std::string, TypedValue> *summary);
|
||||
std::optional<plan::ProfilingStatsWithTotalTime> PullMultiple(AnyStream *stream, std::optional<int> n,
|
||||
const std::vector<Symbol> &output_symbols,
|
||||
std::map<std::string, TypedValue> *summary);
|
||||
|
||||
private:
|
||||
std::shared_ptr<CachedPlan> plan_ = nullptr;
|
||||
plan::UniqueCursorPtr cursor_ = nullptr;
|
||||
Frame frame_;
|
||||
MultiFrame multi_frame_;
|
||||
expr::Frame<TypedValue> frame_;
|
||||
ExecutionContext ctx_;
|
||||
std::optional<size_t> memory_limit_;
|
||||
|
||||
@@ -684,33 +700,29 @@ struct PullPlan {
|
||||
|
||||
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, const bool is_profile_query,
|
||||
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager, const std::optional<size_t> memory_limit)
|
||||
const std::optional<size_t> memory_limit)
|
||||
// TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit)
|
||||
: plan_(plan),
|
||||
cursor_(plan->plan().MakeCursor(execution_memory)),
|
||||
frame_(plan->symbol_table().max_position(), execution_memory),
|
||||
multi_frame_({.frames = utils::pmr::vector<Frame>(1000, frame_, execution_memory), .valid_frames = 0}),
|
||||
memory_limit_(memory_limit) {
|
||||
ctx_.db_accessor = dba;
|
||||
ctx_.symbol_table = plan->symbol_table();
|
||||
ctx_.evaluation_context.timestamp = QueryTimestamp();
|
||||
ctx_.evaluation_context.parameters = parameters;
|
||||
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, shard_request_manager);
|
||||
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, shard_request_manager);
|
||||
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, dba);
|
||||
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
|
||||
if (interpreter_context->config.execution_timeout_sec > 0) {
|
||||
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
|
||||
}
|
||||
ctx_.is_shutting_down = &interpreter_context->is_shutting_down;
|
||||
ctx_.is_profile_query = is_profile_query;
|
||||
ctx_.shard_request_manager = shard_request_manager;
|
||||
ctx_.edge_ids_alloc = &interpreter_context->edge_ids_alloc;
|
||||
// ctx_.trigger_context_collector = trigger_context_collector;
|
||||
}
|
||||
|
||||
std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *stream, std::optional<int> n,
|
||||
const std::vector<Symbol> &output_symbols,
|
||||
std::map<std::string, TypedValue> *summary) {
|
||||
if (!output_symbols.empty() && output_symbols[0].name() == "mmm") {
|
||||
return PullMultiple(stream, n, output_symbols, summary);
|
||||
}
|
||||
// Set up temporary memory for a single Pull. Initial memory comes from the
|
||||
// stack. 256 KiB should fit on the stack and should be more than enough for a
|
||||
// single `Pull`.
|
||||
@@ -797,136 +809,17 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
|
||||
return GetStatsWithTotalTime(ctx_);
|
||||
}
|
||||
|
||||
std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::PullMultiple(AnyStream *stream, std::optional<int> n,
|
||||
const std::vector<Symbol> &output_symbols,
|
||||
std::map<std::string, TypedValue> *summary) {
|
||||
// Set up temporary memory for a single Pull. Initial memory comes from the
|
||||
// stack. 256 KiB should fit on the stack and should be more than enough for a
|
||||
// single `Pull`.
|
||||
MG_ASSERT(!n.has_value(), "should pull all!");
|
||||
static constexpr size_t stack_size = 256UL * 1024UL;
|
||||
char stack_data[stack_size];
|
||||
utils::ResourceWithOutOfMemoryException resource_with_exception;
|
||||
utils::MonotonicBufferResource monotonic_memory(&stack_data[0], stack_size, &resource_with_exception);
|
||||
// We can throw on every query because a simple queries for deleting will use only
|
||||
// the stack allocated buffer.
|
||||
// Also, we want to throw only when the query engine requests more memory and not the storage
|
||||
// so we add the exception to the allocator.
|
||||
// TODO (mferencevic): Tune the parameters accordingly.
|
||||
utils::PoolResource pool_memory(128, 1024, &monotonic_memory);
|
||||
std::optional<utils::LimitedMemoryResource> maybe_limited_resource;
|
||||
|
||||
if (memory_limit_) {
|
||||
maybe_limited_resource.emplace(&pool_memory, *memory_limit_);
|
||||
ctx_.evaluation_context.memory = &*maybe_limited_resource;
|
||||
} else {
|
||||
ctx_.evaluation_context.memory = &pool_memory;
|
||||
}
|
||||
|
||||
// Returns true if a result was pulled.
|
||||
const auto pull_result = [&]() -> bool {
|
||||
cursor_->PullMultiple(multi_frame_, ctx_);
|
||||
return multi_frame_.valid_frames > 0;
|
||||
};
|
||||
|
||||
const auto stream_values = [&output_symbols, &stream](Frame &frame) {
|
||||
// TODO: The streamed values should also probably use the above memory.
|
||||
std::vector<TypedValue> values;
|
||||
values.reserve(output_symbols.size());
|
||||
|
||||
for (const auto &symbol : output_symbols) {
|
||||
values.emplace_back(frame[symbol]);
|
||||
}
|
||||
|
||||
stream->Result(values);
|
||||
};
|
||||
|
||||
// Get the execution time of all possible result pulls and streams.
|
||||
utils::Timer timer;
|
||||
|
||||
int i = 0;
|
||||
if (has_unsent_results_ && !output_symbols.empty()) {
|
||||
// stream unsent results from previous pull
|
||||
for (auto frame_index = 0U; frame_index < multi_frame_.valid_frames; ++frame_index) {
|
||||
stream_values(multi_frame_.frames[frame_index]);
|
||||
++i;
|
||||
}
|
||||
multi_frame_.valid_frames = 0;
|
||||
}
|
||||
|
||||
for (; !n || i < n;) {
|
||||
if (!pull_result()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!output_symbols.empty()) {
|
||||
for (auto frame_index = 0U; frame_index < multi_frame_.valid_frames; ++frame_index) {
|
||||
stream_values(multi_frame_.frames[frame_index]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
multi_frame_.valid_frames = 0;
|
||||
}
|
||||
|
||||
// If we finished because we streamed the requested n results,
|
||||
// we try to pull the next result to see if there is more.
|
||||
// If there is additional result, we leave the pulled result in the frame
|
||||
// and set the flag to true.
|
||||
has_unsent_results_ = i == n && pull_result();
|
||||
|
||||
execution_time_ += timer.Elapsed();
|
||||
|
||||
if (has_unsent_results_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
summary->insert_or_assign("plan_execution_time", execution_time_.count());
|
||||
// We are finished with pulling all the data, therefore we can send any
|
||||
// metadata about the results i.e. notifications and statistics
|
||||
const bool is_any_counter_set =
|
||||
std::any_of(ctx_.execution_stats.counters.begin(), ctx_.execution_stats.counters.end(),
|
||||
[](const auto &counter) { return counter > 0; });
|
||||
if (is_any_counter_set) {
|
||||
std::map<std::string, TypedValue> stats;
|
||||
for (size_t i = 0; i < ctx_.execution_stats.counters.size(); ++i) {
|
||||
stats.emplace(ExecutionStatsKeyToString(ExecutionStats::Key(i)), ctx_.execution_stats.counters[i]);
|
||||
}
|
||||
summary->insert_or_assign("stats", std::move(stats));
|
||||
}
|
||||
cursor_->Shutdown();
|
||||
ctx_.profile_execution_time = execution_time_;
|
||||
return GetStatsWithTotalTime(ctx_);
|
||||
}
|
||||
|
||||
using RWType = plan::ReadWriteTypeChecker::RWType;
|
||||
} // namespace
|
||||
|
||||
InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config,
|
||||
const std::filesystem::path & /*data_directory*/,
|
||||
io::Io<io::local_transport::LocalTransport> io,
|
||||
coordinator::Address coordinator_addr)
|
||||
: db(db), config(config), io{std::move(io)}, coordinator_address{coordinator_addr} {}
|
||||
const std::filesystem::path &data_directory)
|
||||
// : db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory /
|
||||
// "streams"} {}
|
||||
: db(db), config(config) {}
|
||||
|
||||
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
|
||||
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
|
||||
auto query_io = interpreter_context_->io.ForkLocal();
|
||||
shard_request_manager_ = std::make_unique<msgs::ShardRequestManager<io::local_transport::LocalTransport>>(
|
||||
coordinator::CoordinatorClient<io::local_transport::LocalTransport>(
|
||||
query_io, interpreter_context_->coordinator_address, std::vector{interpreter_context_->coordinator_address}),
|
||||
std::move(query_io));
|
||||
// Get edge ids
|
||||
coordinator::CoordinatorWriteRequests requests{coordinator::AllocateEdgeIdBatchRequest{.batch_size = 1000000}};
|
||||
io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests> ww;
|
||||
ww.operation = requests;
|
||||
auto resp = interpreter_context_->io
|
||||
.Request<io::rsm::WriteRequest<coordinator::CoordinatorWriteRequests>,
|
||||
io::rsm::WriteResponse<coordinator::CoordinatorWriteResponses>>(
|
||||
interpreter_context_->coordinator_address, ww)
|
||||
.Wait();
|
||||
if (resp.HasValue()) {
|
||||
const auto alloc_edge_id_reps =
|
||||
std::get<coordinator::AllocateEdgeIdBatchResponse>(resp.GetValue().message.write_return);
|
||||
interpreter_context_->edge_ids_alloc = {alloc_edge_id_reps.low, alloc_edge_id_reps.high};
|
||||
}
|
||||
}
|
||||
|
||||
PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) {
|
||||
@@ -939,6 +832,14 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
}
|
||||
in_explicit_transaction_ = true;
|
||||
expect_rollback_ = false;
|
||||
|
||||
db_accessor_ = std::make_unique<storage::v3::Shard::Accessor>(
|
||||
interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride()));
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
|
||||
// if (interpreter_context_->trigger_store.HasTriggers()) {
|
||||
// trigger_context_collector_.emplace(interpreter_context_->trigger_store.GetEventTypes());
|
||||
// }
|
||||
};
|
||||
} else if (query_upper == "COMMIT") {
|
||||
handler = [this] {
|
||||
@@ -985,18 +886,16 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
|
||||
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager) {
|
||||
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications) {
|
||||
// TriggerContextCollector *trigger_context_collector = nullptr) {
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
|
||||
|
||||
Frame frame(0);
|
||||
expr::Frame<TypedValue> frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
evaluation_context.parameters = parsed_query.parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, shard_request_manager,
|
||||
storage::v3::View::OLD);
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::v3::View::OLD);
|
||||
const auto memory_limit =
|
||||
expr::EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
|
||||
if (memory_limit) {
|
||||
@@ -1011,9 +910,10 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
"convert the parsed row values to the appropriate type. This can be done using the built-in "
|
||||
"conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
|
||||
}
|
||||
auto plan = CypherQueryToPlan(
|
||||
parsed_query.stripped_query.hash(), std::move(parsed_query.ast_storage), cypher_query, parsed_query.parameters,
|
||||
parsed_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, shard_request_manager);
|
||||
|
||||
auto plan = CypherQueryToPlan(parsed_query.stripped_query.hash(), std::move(parsed_query.ast_storage), cypher_query,
|
||||
parsed_query.parameters,
|
||||
parsed_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
|
||||
|
||||
summary->insert_or_assign("cost_estimate", plan->cost());
|
||||
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
||||
@@ -1032,7 +932,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
|
||||
}
|
||||
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
|
||||
execution_memory, shard_request_manager, memory_limit);
|
||||
execution_memory, memory_limit);
|
||||
// execution_memory, trigger_context_collector, memory_limit);
|
||||
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
|
||||
[pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary](
|
||||
@@ -1046,8 +946,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
}
|
||||
|
||||
PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
||||
InterpreterContext *interpreter_context,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
utils::MemoryResource *execution_memory) {
|
||||
const std::string kExplainQueryStart = "explain ";
|
||||
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kExplainQueryStart),
|
||||
@@ -1068,18 +967,17 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
||||
|
||||
auto cypher_query_plan = CypherQueryToPlan(
|
||||
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
|
||||
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr,
|
||||
shard_request_manager);
|
||||
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
|
||||
|
||||
std::stringstream printed_plan;
|
||||
plan::PrettyPrint(*shard_request_manager, &cypher_query_plan->plan(), &printed_plan);
|
||||
plan::PrettyPrint(*dba, &cypher_query_plan->plan(), &printed_plan);
|
||||
|
||||
std::vector<std::vector<TypedValue>> printed_plan_rows;
|
||||
for (const auto &row : utils::Split(utils::RTrim(printed_plan.str()), "\n")) {
|
||||
printed_plan_rows.push_back(std::vector<TypedValue>{TypedValue(row)});
|
||||
}
|
||||
|
||||
summary->insert_or_assign("explain", plan::PlanToJson(*shard_request_manager, &cypher_query_plan->plan()).dump());
|
||||
summary->insert_or_assign("explain", plan::PlanToJson(*dba, &cypher_query_plan->plan()).dump());
|
||||
|
||||
return PreparedQuery{{"QUERY PLAN"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
@@ -1095,8 +993,7 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
|
||||
|
||||
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory,
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager = nullptr) {
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory) {
|
||||
const std::string kProfileQueryStart = "profile ";
|
||||
|
||||
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
|
||||
@@ -1134,27 +1031,25 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_inner_query.query);
|
||||
MG_ASSERT(cypher_query, "Cypher grammar should not allow other queries in PROFILE");
|
||||
Frame frame(0);
|
||||
expr::Frame<TypedValue> frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
evaluation_context.timestamp = QueryTimestamp();
|
||||
evaluation_context.parameters = parsed_inner_query.parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, shard_request_manager,
|
||||
storage::v3::View::OLD);
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::v3::View::OLD);
|
||||
const auto memory_limit =
|
||||
expr::EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
|
||||
|
||||
auto cypher_query_plan = CypherQueryToPlan(
|
||||
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
|
||||
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr,
|
||||
shard_request_manager);
|
||||
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
|
||||
auto rw_type_checker = plan::ReadWriteTypeChecker();
|
||||
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
|
||||
|
||||
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME", "CUSTOM DATA"},
|
||||
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[plan = std::move(cypher_query_plan), parameters = std::move(parsed_inner_query.parameters),
|
||||
summary, dba, interpreter_context, execution_memory, memory_limit, shard_request_manager,
|
||||
summary, dba, interpreter_context, execution_memory, memory_limit,
|
||||
// We want to execute the query we are profiling lazily, so we delay
|
||||
// the construction of the corresponding context.
|
||||
stats_and_total_time = std::optional<plan::ProfilingStatsWithTotalTime>{},
|
||||
@@ -1163,8 +1058,8 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
// No output symbols are given so that nothing is streamed.
|
||||
if (!stats_and_total_time) {
|
||||
stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context,
|
||||
execution_memory, shard_request_manager, memory_limit)
|
||||
.PullMultiple(stream, {}, {}, summary);
|
||||
execution_memory, memory_limit)
|
||||
.Pull(stream, {}, {}, summary);
|
||||
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
|
||||
}
|
||||
|
||||
@@ -1182,7 +1077,16 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
|
||||
|
||||
PreparedQuery PrepareDumpQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary, DbAccessor *dba,
|
||||
utils::MemoryResource *execution_memory) {
|
||||
throw QueryRuntimeException("Dump query is not supported!");
|
||||
return PreparedQuery{{"QUERY"},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[pull_plan = std::make_shared<PullPlanDump>(dba)](
|
||||
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
return QueryHandlerResult::COMMIT;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
RWType::R};
|
||||
}
|
||||
|
||||
PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
@@ -1289,15 +1193,14 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
|
||||
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory,
|
||||
msgs::ShardRequestManagerInterface *manager) {
|
||||
DbAccessor *dba, utils::MemoryResource *execution_memory) {
|
||||
if (in_explicit_transaction) {
|
||||
throw UserModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *auth_query = utils::Downcast<AuthQuery>(parsed_query.query);
|
||||
|
||||
auto callback = HandleAuthQuery(auth_query, interpreter_context->auth, parsed_query.parameters, manager);
|
||||
auto callback = HandleAuthQuery(auth_query, interpreter_context->auth, parsed_query.parameters, dba);
|
||||
|
||||
SymbolTable symbol_table;
|
||||
std::vector<Symbol> output_symbols;
|
||||
@@ -1326,14 +1229,14 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
|
||||
PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
std::vector<Notification> *notifications, InterpreterContext *interpreter_context,
|
||||
msgs::ShardRequestManagerInterface *manager) {
|
||||
DbAccessor *dba) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ReplicationModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
|
||||
auto callback =
|
||||
HandleReplicationQuery(replication_query, parsed_query.parameters, interpreter_context, manager, notifications);
|
||||
HandleReplicationQuery(replication_query, parsed_query.parameters, interpreter_context, dba, notifications);
|
||||
|
||||
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -1421,15 +1324,14 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli
|
||||
throw SemanticException("CreateSnapshot query is not supported!");
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSettingQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
msgs::ShardRequestManagerInterface *manager) {
|
||||
PreparedQuery PrepareSettingQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, DbAccessor *dba) {
|
||||
if (in_explicit_transaction) {
|
||||
throw SettingConfigInMulticommandTxException{};
|
||||
}
|
||||
|
||||
auto *setting_query = utils::Downcast<SettingQuery>(parsed_query.query);
|
||||
MG_ASSERT(setting_query);
|
||||
auto callback = HandleSettingQuery(setting_query, parsed_query.parameters, manager);
|
||||
auto callback = HandleSettingQuery(setting_query, parsed_query.parameters, dba);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -1623,10 +1525,15 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
ParsedQuery parsed_query =
|
||||
ParseQuery(query_string, params, &interpreter_context_->ast_cache, interpreter_context_->config.query);
|
||||
query_execution->summary["parsing_time"] = parsing_timer.Elapsed().count();
|
||||
|
||||
// Some queries require an active transaction in order to be prepared.
|
||||
if (!in_explicit_transaction_ &&
|
||||
(utils::Downcast<CypherQuery>(parsed_query.query) || utils::Downcast<ExplainQuery>(parsed_query.query) ||
|
||||
utils::Downcast<ProfileQuery>(parsed_query.query))) {
|
||||
shard_request_manager_->StartTransaction();
|
||||
utils::Downcast<ProfileQuery>(parsed_query.query) || utils::Downcast<DumpQuery>(parsed_query.query) ||
|
||||
utils::Downcast<TriggerQuery>(parsed_query.query))) {
|
||||
db_accessor_ = std::make_unique<storage::v3::Shard::Accessor>(
|
||||
interpreter_context_->db->Access(coordinator::Hlc{}, GetIsolationLevelOverride()));
|
||||
execution_db_accessor_.emplace(db_accessor_.get());
|
||||
}
|
||||
|
||||
utils::Timer planning_timer;
|
||||
@@ -1635,14 +1542,14 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory,
|
||||
&query_execution->notifications, shard_request_manager_.get());
|
||||
&query_execution->notifications);
|
||||
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*shard_request_manager_, &query_execution->execution_memory_with_exception);
|
||||
&*execution_db_accessor_, &query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareProfileQuery(
|
||||
std::move(parsed_query), in_explicit_transaction_, &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory_with_exception, shard_request_manager_.get());
|
||||
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory);
|
||||
@@ -1650,9 +1557,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_,
|
||||
&query_execution->notifications, interpreter_context_);
|
||||
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareAuthQuery(
|
||||
std::move(parsed_query), in_explicit_transaction_, &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory_with_exception, shard_request_manager_.get());
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, interpreter_context_->db,
|
||||
@@ -1663,7 +1570,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
interpreter_context_, shard_request_manager_.get());
|
||||
interpreter_context_, &*execution_db_accessor_);
|
||||
} else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_);
|
||||
@@ -1680,8 +1587,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query =
|
||||
PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
|
||||
} else if (utils::Downcast<SettingQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, shard_request_manager_.get());
|
||||
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)) {
|
||||
@@ -1722,7 +1628,6 @@ void Interpreter::Commit() {
|
||||
// For now, we will not check if there are some unfinished queries.
|
||||
// We should document clearly that all results should be pulled to complete
|
||||
// a query.
|
||||
shard_request_manager_->Commit();
|
||||
if (!db_accessor_) return;
|
||||
|
||||
const auto reset_necessary_members = [this]() {
|
||||
|
||||
@@ -12,12 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "coordinator/coordinator.hpp"
|
||||
#include "coordinator/coordinator_client.hpp"
|
||||
#include "io/local_transport/local_transport.hpp"
|
||||
#include "io/transport.hpp"
|
||||
#include "query/v2/auth_checker.hpp"
|
||||
#include "query/v2/bindings/cypher_main_visitor.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
@@ -31,7 +26,6 @@
|
||||
#include "query/v2/metadata.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/read_write_type_checker.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "query/v2/stream.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
@@ -171,8 +165,7 @@ struct PreparedQuery {
|
||||
*/
|
||||
struct InterpreterContext {
|
||||
explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory,
|
||||
io::Io<io::local_transport::LocalTransport> io, coordinator::Address coordinator_addr);
|
||||
const std::filesystem::path &data_directory);
|
||||
|
||||
storage::v3::Shard *db;
|
||||
|
||||
@@ -186,12 +179,6 @@ struct InterpreterContext {
|
||||
utils::SkipList<PlanCacheEntry> plan_cache;
|
||||
|
||||
const InterpreterConfig config;
|
||||
IdAllocator edge_ids_alloc;
|
||||
|
||||
// TODO (antaljanosbenjamin) Figure out an abstraction for io::Io to make it possible to construct an interpreter
|
||||
// context with a simulator transport without templatizing it.
|
||||
io::Io<io::local_transport::LocalTransport> io;
|
||||
coordinator::Address coordinator_address;
|
||||
|
||||
storage::v3::LabelId NameToLabelId(std::string_view label_name) {
|
||||
return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name));
|
||||
@@ -296,8 +283,6 @@ class Interpreter final {
|
||||
*/
|
||||
void Abort();
|
||||
|
||||
const msgs::ShardRequestManagerInterface *GetShardRequestManager() const { return shard_request_manager_.get(); }
|
||||
|
||||
private:
|
||||
struct QueryExecution {
|
||||
std::optional<PreparedQuery> prepared_query;
|
||||
@@ -339,10 +324,9 @@ class Interpreter final {
|
||||
|
||||
// This cannot be std::optional because we need to move this accessor later on into a lambda capture
|
||||
// which is assigned to std::function. std::function requires every object to be copyable, so we
|
||||
// move this unique_ptr into a shared_ptr.
|
||||
// move this unique_ptr into a shrared_ptr.
|
||||
std::unique_ptr<storage::v3::Shard::Accessor> db_accessor_;
|
||||
std::optional<DbAccessor> execution_db_accessor_;
|
||||
std::unique_ptr<msgs::ShardRequestManagerInterface> shard_request_manager_;
|
||||
bool in_explicit_transaction_{false};
|
||||
bool expect_rollback_{false};
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
@@ -32,7 +32,7 @@ struct Parameters {
|
||||
* @param position Token position in query of value.
|
||||
* @param value
|
||||
*/
|
||||
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace(position, value); }
|
||||
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace_back(position, value); }
|
||||
|
||||
/**
|
||||
* Returns the value found for the given token position.
|
||||
@@ -41,11 +41,23 @@ struct Parameters {
|
||||
* @return Value for the given token position.
|
||||
*/
|
||||
const storage::v3::PropertyValue &AtTokenPosition(int position) const {
|
||||
auto found = storage_.find(position);
|
||||
auto found = std::find_if(storage_.begin(), storage_.end(), [&](const auto &a) { return a.first == position; });
|
||||
MG_ASSERT(found != storage_.end(), "Token position must be present in container");
|
||||
return found->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position-th stripped value. Asserts that this
|
||||
* container has at least (position + 1) elements.
|
||||
*
|
||||
* @param position Which stripped param is sought.
|
||||
* @return Token position and value for sought param.
|
||||
*/
|
||||
const std::pair<int, storage::v3::PropertyValue> &At(int position) const {
|
||||
MG_ASSERT(position < static_cast<int>(storage_.size()), "Invalid position");
|
||||
return storage_[position];
|
||||
}
|
||||
|
||||
/** Returns the number of arguments in this container */
|
||||
auto size() const { return storage_.size(); }
|
||||
|
||||
@@ -53,7 +65,7 @@ struct Parameters {
|
||||
auto end() const { return storage_.end(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<int, storage::v3::PropertyValue> storage_;
|
||||
std::vector<std::pair<int, storage::v3::PropertyValue>> storage_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
@@ -30,8 +28,6 @@ namespace memgraph::query::v2 {
|
||||
*/
|
||||
class Path {
|
||||
public:
|
||||
using VertexAccessor = accessors::VertexAccessor;
|
||||
using EdgeAccessor = accessors::EdgeAccessor;
|
||||
/** Allocator type so that STL containers are aware that we need one */
|
||||
using allocator_type = utils::Allocator<char>;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,9 +22,9 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "expr/semantic/symbol.hpp"
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "expr/semantic/symbol.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/bindings/frame.hpp"
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
@@ -71,8 +71,6 @@ class Cursor {
|
||||
/// @throws QueryRuntimeException if something went wrong with execution
|
||||
virtual bool Pull(Frame &, ExecutionContext &) = 0;
|
||||
|
||||
virtual void PullMultiple(MultiFrame &, ExecutionContext &) { LOG_FATAL("PullMultipleIsNotImplemented"); }
|
||||
|
||||
/// Resets the Cursor to its initial state.
|
||||
virtual void Reset() = 0;
|
||||
|
||||
@@ -335,12 +333,11 @@ and false on every following Pull.")
|
||||
public:
|
||||
OnceCursor() {}
|
||||
bool Pull(Frame &, ExecutionContext &) override;
|
||||
void PullMultiple(MultiFrame &, ExecutionContext &) override;
|
||||
void Shutdown() override;
|
||||
void Reset() override;
|
||||
|
||||
private:
|
||||
size_t pull_count_{false};
|
||||
bool did_pull_{false};
|
||||
};
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
@@ -1210,7 +1207,6 @@ RETURN clause) the Produce's pull succeeds exactly once.")
|
||||
public:
|
||||
ProduceCursor(const Produce &, utils::MemoryResource *);
|
||||
bool Pull(Frame &, ExecutionContext &) override;
|
||||
void PullMultiple(MultiFrame &, ExecutionContext &) override;
|
||||
void Shutdown() override;
|
||||
void Reset() override;
|
||||
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
|
||||
#include "query/v2/bindings/pretty_print.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
|
||||
PlanPrinter::PlanPrinter(const msgs::ShardRequestManagerInterface *request_manager, std::ostream *out)
|
||||
: request_manager_(request_manager), out_(out) {}
|
||||
PlanPrinter::PlanPrinter(const DbAccessor *dba, std::ostream *out) : dba_(dba), out_(out) {}
|
||||
|
||||
#define PRE_VISIT(TOp) \
|
||||
bool PlanPrinter::PreVisit(TOp &) { \
|
||||
@@ -34,7 +32,7 @@ bool PlanPrinter::PreVisit(CreateExpand &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* CreateExpand (" << op.input_symbol_.name() << ")"
|
||||
<< (op.edge_info_.direction == query::v2::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.edge_info_.symbol.name() << ":" << request_manager_->EdgeTypeToName(op.edge_info_.edge_type) << "]"
|
||||
<< op.edge_info_.symbol.name() << ":" << dba_->EdgeTypeToName(op.edge_info_.edge_type) << "]"
|
||||
<< (op.edge_info_.direction == query::v2::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.node_info_.symbol.name() << ")";
|
||||
});
|
||||
@@ -54,7 +52,7 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAll &op) {
|
||||
bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabel &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabel"
|
||||
<< " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << ")";
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << ")";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -62,8 +60,8 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabel &op) {
|
||||
bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelPropertyValue &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyValue"
|
||||
<< " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << " {"
|
||||
<< request_manager_->PropertyToName(op.property_) << "})";
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -71,8 +69,8 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelPropertyValue &op) {
|
||||
bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelPropertyRange &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyRange"
|
||||
<< " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << " {"
|
||||
<< request_manager_->PropertyToName(op.property_) << "})";
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -80,8 +78,8 @@ bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelPropertyRange &op) {
|
||||
bool PlanPrinter::PreVisit(query::v2::plan::ScanAllByLabelProperty &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelProperty"
|
||||
<< " (" << op.output_symbol_.name() << " :" << request_manager_->LabelToName(op.label_) << " {"
|
||||
<< request_manager_->PropertyToName(op.property_) << "})";
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -100,7 +98,7 @@ bool PlanPrinter::PreVisit(query::v2::plan::Expand &op) {
|
||||
<< (op.common_.direction == query::v2::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.common_.edge_symbol.name();
|
||||
utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) {
|
||||
stream << ":" << request_manager_->EdgeTypeToName(edge_type);
|
||||
stream << ":" << dba_->EdgeTypeToName(edge_type);
|
||||
});
|
||||
*out_ << "]" << (op.common_.direction == query::v2::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.common_.node_symbol.name() << ")";
|
||||
@@ -129,7 +127,7 @@ bool PlanPrinter::PreVisit(query::v2::plan::ExpandVariable &op) {
|
||||
<< (op.common_.direction == query::v2::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.common_.edge_symbol.name();
|
||||
utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) {
|
||||
stream << ":" << request_manager_->EdgeTypeToName(edge_type);
|
||||
stream << ":" << dba_->EdgeTypeToName(edge_type);
|
||||
});
|
||||
*out_ << "]" << (op.common_.direction == query::v2::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.common_.node_symbol.name() << ")";
|
||||
@@ -263,15 +261,14 @@ void PlanPrinter::Branch(query::v2::plan::LogicalOperator &op, const std::string
|
||||
--depth_;
|
||||
}
|
||||
|
||||
void PrettyPrint(const msgs::ShardRequestManagerInterface &request_manager, const LogicalOperator *plan_root,
|
||||
std::ostream *out) {
|
||||
PlanPrinter printer(&request_manager, out);
|
||||
void PrettyPrint(const DbAccessor &dba, const LogicalOperator *plan_root, std::ostream *out) {
|
||||
PlanPrinter printer(&dba, out);
|
||||
// FIXME(mtomic): We should make visitors that take const arguments.
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(printer);
|
||||
}
|
||||
|
||||
nlohmann::json PlanToJson(const msgs::ShardRequestManagerInterface &request_manager, const LogicalOperator *plan_root) {
|
||||
impl::PlanToJsonVisitor visitor(&request_manager);
|
||||
nlohmann::json PlanToJson(const DbAccessor &dba, const LogicalOperator *plan_root) {
|
||||
impl::PlanToJsonVisitor visitor(&dba);
|
||||
// FIXME(mtomic): We should make visitors that take const arguments.
|
||||
const_cast<LogicalOperator *>(plan_root)->Accept(visitor);
|
||||
return visitor.output();
|
||||
@@ -349,17 +346,11 @@ json ToJson(const utils::Bound<Expression *> &bound) {
|
||||
|
||||
json ToJson(const Symbol &symbol) { return symbol.name(); }
|
||||
|
||||
json ToJson(storage::v3::EdgeTypeId edge_type, const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
return request_manager.EdgeTypeToName(edge_type);
|
||||
}
|
||||
json ToJson(storage::v3::EdgeTypeId edge_type, const DbAccessor &dba) { return dba.EdgeTypeToName(edge_type); }
|
||||
|
||||
json ToJson(storage::v3::LabelId label, const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
return request_manager.LabelToName(label);
|
||||
}
|
||||
json ToJson(storage::v3::LabelId label, const DbAccessor &dba) { return dba.LabelToName(label); }
|
||||
|
||||
json ToJson(storage::v3::PropertyId property, const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
return request_manager.PropertyToName(property);
|
||||
}
|
||||
json ToJson(storage::v3::PropertyId property, const DbAccessor &dba) { return dba.PropertyToName(property); }
|
||||
|
||||
json ToJson(NamedExpression *nexpr) {
|
||||
json json;
|
||||
@@ -368,30 +359,29 @@ json ToJson(NamedExpression *nexpr) {
|
||||
return json;
|
||||
}
|
||||
|
||||
json ToJson(const std::vector<std::pair<storage::v3::PropertyId, Expression *>> &properties,
|
||||
const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
json ToJson(const std::vector<std::pair<storage::v3::PropertyId, Expression *>> &properties, const DbAccessor &dba) {
|
||||
json json;
|
||||
for (const auto &prop_pair : properties) {
|
||||
json.emplace(ToJson(prop_pair.first, request_manager), ToJson(prop_pair.second));
|
||||
json.emplace(ToJson(prop_pair.first, dba), ToJson(prop_pair.second));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
json ToJson(const NodeCreationInfo &node_info, const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
json ToJson(const NodeCreationInfo &node_info, const DbAccessor &dba) {
|
||||
json self;
|
||||
self["symbol"] = ToJson(node_info.symbol);
|
||||
self["labels"] = ToJson(node_info.labels, request_manager);
|
||||
self["labels"] = ToJson(node_info.labels, dba);
|
||||
const auto *props = std::get_if<PropertiesMapList>(&node_info.properties);
|
||||
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, request_manager);
|
||||
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, dba);
|
||||
return self;
|
||||
}
|
||||
|
||||
json ToJson(const EdgeCreationInfo &edge_info, const msgs::ShardRequestManagerInterface &request_manager) {
|
||||
json ToJson(const EdgeCreationInfo &edge_info, const DbAccessor &dba) {
|
||||
json self;
|
||||
self["symbol"] = ToJson(edge_info.symbol);
|
||||
const auto *props = std::get_if<PropertiesMapList>(&edge_info.properties);
|
||||
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, request_manager);
|
||||
self["edge_type"] = ToJson(edge_info.edge_type, request_manager);
|
||||
self["properties"] = ToJson(props ? *props : PropertiesMapList{}, dba);
|
||||
self["edge_type"] = ToJson(edge_info.edge_type, dba);
|
||||
self["direction"] = ToString(edge_info.direction);
|
||||
return self;
|
||||
}
|
||||
@@ -433,7 +423,7 @@ bool PlanToJsonVisitor::PreVisit(ScanAll &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabel &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabel";
|
||||
self["label"] = ToJson(op.label_, *request_manager_);
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
@@ -446,8 +436,8 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabel &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyRange &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabelPropertyRange";
|
||||
self["label"] = ToJson(op.label_, *request_manager_);
|
||||
self["property"] = ToJson(op.property_, *request_manager_);
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lower_bound"] = op.lower_bound_ ? ToJson(*op.lower_bound_) : json();
|
||||
self["upper_bound"] = op.upper_bound_ ? ToJson(*op.upper_bound_) : json();
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
@@ -462,8 +452,8 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyRange &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyValue &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabelPropertyValue";
|
||||
self["label"] = ToJson(op.label_, *request_manager_);
|
||||
self["property"] = ToJson(op.property_, *request_manager_);
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["expression"] = ToJson(op.expression_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
@@ -477,8 +467,8 @@ bool PlanToJsonVisitor::PreVisit(ScanAllByLabelPropertyValue &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(ScanAllByLabelProperty &op) {
|
||||
json self;
|
||||
self["name"] = "ScanAllByLabelProperty";
|
||||
self["label"] = ToJson(op.label_, *request_manager_);
|
||||
self["property"] = ToJson(op.property_, *request_manager_);
|
||||
self["label"] = ToJson(op.label_, *dba_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["output_symbol"] = ToJson(op.output_symbol_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
@@ -501,7 +491,7 @@ bool PlanToJsonVisitor::PreVisit(ScanAllById &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(CreateNode &op) {
|
||||
json self;
|
||||
self["name"] = "CreateNode";
|
||||
self["node_info"] = ToJson(op.node_info_, *request_manager_);
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
@@ -514,8 +504,8 @@ bool PlanToJsonVisitor::PreVisit(CreateExpand &op) {
|
||||
json self;
|
||||
self["name"] = "CreateExpand";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_info"] = ToJson(op.node_info_, *request_manager_);
|
||||
self["edge_info"] = ToJson(op.edge_info_, *request_manager_);
|
||||
self["node_info"] = ToJson(op.node_info_, *dba_);
|
||||
self["edge_info"] = ToJson(op.edge_info_, *dba_);
|
||||
self["existing_node"] = op.existing_node_;
|
||||
|
||||
op.input_->Accept(*this);
|
||||
@@ -531,7 +521,7 @@ bool PlanToJsonVisitor::PreVisit(Expand &op) {
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *request_manager_);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["existing_node"] = op.common_.existing_node;
|
||||
|
||||
@@ -548,7 +538,7 @@ bool PlanToJsonVisitor::PreVisit(ExpandVariable &op) {
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["node_symbol"] = ToJson(op.common_.node_symbol);
|
||||
self["edge_symbol"] = ToJson(op.common_.edge_symbol);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *request_manager_);
|
||||
self["edge_types"] = ToJson(op.common_.edge_types, *dba_);
|
||||
self["direction"] = ToString(op.common_.direction);
|
||||
self["type"] = ToString(op.type_);
|
||||
self["is_reverse"] = op.is_reverse_;
|
||||
@@ -623,7 +613,7 @@ bool PlanToJsonVisitor::PreVisit(Delete &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(SetProperty &op) {
|
||||
json self;
|
||||
self["name"] = "SetProperty";
|
||||
self["property"] = ToJson(op.property_, *request_manager_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lhs"] = ToJson(op.lhs_);
|
||||
self["rhs"] = ToJson(op.rhs_);
|
||||
|
||||
@@ -660,7 +650,7 @@ bool PlanToJsonVisitor::PreVisit(SetLabels &op) {
|
||||
json self;
|
||||
self["name"] = "SetLabels";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["labels"] = ToJson(op.labels_, *request_manager_);
|
||||
self["labels"] = ToJson(op.labels_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
@@ -672,7 +662,7 @@ bool PlanToJsonVisitor::PreVisit(SetLabels &op) {
|
||||
bool PlanToJsonVisitor::PreVisit(RemoveProperty &op) {
|
||||
json self;
|
||||
self["name"] = "RemoveProperty";
|
||||
self["property"] = ToJson(op.property_, *request_manager_);
|
||||
self["property"] = ToJson(op.property_, *dba_);
|
||||
self["lhs"] = ToJson(op.lhs_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
@@ -686,7 +676,7 @@ bool PlanToJsonVisitor::PreVisit(RemoveLabels &op) {
|
||||
json self;
|
||||
self["name"] = "RemoveLabels";
|
||||
self["input_symbol"] = ToJson(op.input_symbol_);
|
||||
self["labels"] = ToJson(op.labels_, *request_manager_);
|
||||
self["labels"] = ToJson(op.labels_, *dba_);
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
@@ -18,29 +18,28 @@
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
class DbAccessor;
|
||||
|
||||
namespace plan {
|
||||
|
||||
class LogicalOperator;
|
||||
|
||||
/// Pretty print a `LogicalOperator` plan to a `std::ostream`.
|
||||
/// ShardRequestManager is needed for resolving label and property names.
|
||||
/// DbAccessor is needed for resolving label and property names.
|
||||
/// Note that `plan_root` isn't modified, but we can't take it as a const
|
||||
/// because we don't have support for visiting a const LogicalOperator.
|
||||
void PrettyPrint(const msgs::ShardRequestManagerInterface &request_manager, const LogicalOperator *plan_root,
|
||||
std::ostream *out);
|
||||
void PrettyPrint(const DbAccessor &dba, const LogicalOperator *plan_root, std::ostream *out);
|
||||
|
||||
/// Overload of `PrettyPrint` which defaults the `std::ostream` to `std::cout`.
|
||||
inline void PrettyPrint(const msgs::ShardRequestManagerInterface &request_manager, const LogicalOperator *plan_root) {
|
||||
PrettyPrint(request_manager, plan_root, &std::cout);
|
||||
inline void PrettyPrint(const DbAccessor &dba, const LogicalOperator *plan_root) {
|
||||
PrettyPrint(dba, plan_root, &std::cout);
|
||||
}
|
||||
|
||||
/// Convert a `LogicalOperator` plan to a JSON representation.
|
||||
/// DbAccessor is needed for resolving label and property names.
|
||||
nlohmann::json PlanToJson(const msgs::ShardRequestManagerInterface &request_manager, const LogicalOperator *plan_root);
|
||||
nlohmann::json PlanToJson(const DbAccessor &dba, const LogicalOperator *plan_root);
|
||||
|
||||
class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
@@ -48,7 +47,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::Visit;
|
||||
|
||||
PlanPrinter(const msgs::ShardRequestManagerInterface *request_manager, std::ostream *out);
|
||||
PlanPrinter(const DbAccessor *dba, std::ostream *out);
|
||||
|
||||
bool DefaultPreVisit() override;
|
||||
|
||||
@@ -115,7 +114,7 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
void Branch(LogicalOperator &op, const std::string &branch_name = "");
|
||||
|
||||
int64_t depth_{0};
|
||||
const msgs::ShardRequestManagerInterface *request_manager_{nullptr};
|
||||
const DbAccessor *dba_{nullptr};
|
||||
std::ostream *out_{nullptr};
|
||||
};
|
||||
|
||||
@@ -133,20 +132,20 @@ nlohmann::json ToJson(const utils::Bound<Expression *> &bound);
|
||||
|
||||
nlohmann::json ToJson(const Symbol &symbol);
|
||||
|
||||
nlohmann::json ToJson(storage::v3::EdgeTypeId edge_type, const msgs::ShardRequestManagerInterface &request_manager);
|
||||
nlohmann::json ToJson(storage::v3::EdgeTypeId edge_type, const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(storage::v3::LabelId label, const msgs::ShardRequestManagerInterface &request_manager);
|
||||
nlohmann::json ToJson(storage::v3::LabelId label, const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(storage::v3::PropertyId property, const msgs::ShardRequestManagerInterface &request_manager);
|
||||
nlohmann::json ToJson(storage::v3::PropertyId property, const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(NamedExpression *nexpr);
|
||||
|
||||
nlohmann::json ToJson(const std::vector<std::pair<storage::v3::PropertyId, Expression *>> &properties,
|
||||
const msgs::ShardRequestManagerInterface &request_manager);
|
||||
const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const NodeCreationInfo &node_info, const msgs::ShardRequestManagerInterface &request_manager);
|
||||
nlohmann::json ToJson(const NodeCreationInfo &node_info, const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const EdgeCreationInfo &edge_info, const msgs::ShardRequestManagerInterface &request_manager);
|
||||
nlohmann::json ToJson(const EdgeCreationInfo &edge_info, const DbAccessor &dba);
|
||||
|
||||
nlohmann::json ToJson(const Aggregate::Element &elem);
|
||||
|
||||
@@ -161,8 +160,7 @@ nlohmann::json ToJson(const std::vector<T> &items, Args &&...args) {
|
||||
|
||||
class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
explicit PlanToJsonVisitor(const msgs::ShardRequestManagerInterface *request_manager)
|
||||
: request_manager_(request_manager) {}
|
||||
explicit PlanToJsonVisitor(const DbAccessor *dba) : dba_(dba) {}
|
||||
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
@@ -218,7 +216,7 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
|
||||
protected:
|
||||
nlohmann::json output_;
|
||||
const msgs::ShardRequestManagerInterface *request_manager_;
|
||||
const DbAccessor *dba_;
|
||||
|
||||
nlohmann::json PopOutput() {
|
||||
nlohmann::json tmp;
|
||||
|
||||
@@ -24,18 +24,18 @@ namespace memgraph::query::v2::plan {
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t IndividualCycles(const ProfilingStats &cumulative_stats) {
|
||||
unsigned long long IndividualCycles(const ProfilingStats &cumulative_stats) {
|
||||
return cumulative_stats.num_cycles - std::accumulate(cumulative_stats.children.begin(),
|
||||
cumulative_stats.children.end(), 0ULL,
|
||||
[](auto acc, auto &stats) { return acc + stats.num_cycles; });
|
||||
}
|
||||
|
||||
double RelativeTime(const uint64_t num_cycles, const uint64_t total_cycles) {
|
||||
return static_cast<double>(num_cycles) / static_cast<double>(total_cycles);
|
||||
double RelativeTime(unsigned long long num_cycles, unsigned long long total_cycles) {
|
||||
return static_cast<double>(num_cycles) / total_cycles;
|
||||
}
|
||||
|
||||
double AbsoluteTime(const uint64_t num_cycles, const uint64_t total_cycles,
|
||||
const std::chrono::duration<double> total_time) {
|
||||
double AbsoluteTime(unsigned long long num_cycles, unsigned long long total_cycles,
|
||||
std::chrono::duration<double> total_time) {
|
||||
return (RelativeTime(num_cycles, total_cycles) * static_cast<std::chrono::duration<double, std::milli>>(total_time))
|
||||
.count();
|
||||
}
|
||||
@@ -50,29 +50,26 @@ namespace {
|
||||
|
||||
class ProfilingStatsToTableHelper {
|
||||
public:
|
||||
ProfilingStatsToTableHelper(uint64_t total_cycles, std::chrono::duration<double> total_time)
|
||||
ProfilingStatsToTableHelper(unsigned long long total_cycles, std::chrono::duration<double> total_time)
|
||||
: total_cycles_(total_cycles), total_time_(total_time) {}
|
||||
|
||||
void Output(const ProfilingStats &cumulative_stats) {
|
||||
auto cycles = IndividualCycles(cumulative_stats);
|
||||
auto custom_data_copy = cumulative_stats.custom_data;
|
||||
ConvertCyclesToTime(custom_data_copy);
|
||||
|
||||
rows_.emplace_back(
|
||||
std::vector<TypedValue>{TypedValue(FormatOperator(cumulative_stats.name)),
|
||||
TypedValue(cumulative_stats.actual_hits), TypedValue(FormatRelativeTime(cycles)),
|
||||
TypedValue(FormatAbsoluteTime(cycles)), TypedValue(custom_data_copy.dump())});
|
||||
rows_.emplace_back(std::vector<TypedValue>{
|
||||
TypedValue(FormatOperator(cumulative_stats.name)), TypedValue(cumulative_stats.actual_hits),
|
||||
TypedValue(FormatRelativeTime(cycles)), TypedValue(FormatAbsoluteTime(cycles))});
|
||||
|
||||
for (size_t i = 1; i < cumulative_stats.children.size(); ++i) {
|
||||
Branch(cumulative_stats.children[i]);
|
||||
}
|
||||
|
||||
if (!cumulative_stats.children.empty()) {
|
||||
if (cumulative_stats.children.size() >= 1) {
|
||||
Output(cumulative_stats.children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<TypedValue>> rows() const { return rows_; }
|
||||
std::vector<std::vector<TypedValue>> rows() { return rows_; }
|
||||
|
||||
private:
|
||||
void Branch(const ProfilingStats &cumulative_stats) {
|
||||
@@ -83,28 +80,7 @@ class ProfilingStatsToTableHelper {
|
||||
--depth_;
|
||||
}
|
||||
|
||||
double AbsoluteTime(const uint64_t cycles) const { return plan::AbsoluteTime(cycles, total_cycles_, total_time_); }
|
||||
|
||||
double RelativeTime(const uint64_t cycles) const { return plan::RelativeTime(cycles, total_cycles_); }
|
||||
|
||||
void ConvertCyclesToTime(nlohmann::json &custom_data) const {
|
||||
const auto convert_cycles_in_json = [this](nlohmann::json &json) {
|
||||
if (!json.is_object()) {
|
||||
return;
|
||||
}
|
||||
if (auto it = json.find(ProfilingStats::kNumCycles); it != json.end()) {
|
||||
auto num_cycles = it.value().get<uint64_t>();
|
||||
json[ProfilingStats::kAbsoluteTime] = AbsoluteTime(num_cycles);
|
||||
json[ProfilingStats::kRelativeTime] = RelativeTime(num_cycles);
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &json : custom_data) {
|
||||
convert_cycles_in_json(json);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Format(const char *str) const {
|
||||
std::string Format(const char *str) {
|
||||
std::ostringstream ss;
|
||||
for (int64_t i = 0; i < depth_; ++i) {
|
||||
ss << "| ";
|
||||
@@ -113,21 +89,21 @@ class ProfilingStatsToTableHelper {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Format(const std::string &str) const { return Format(str.c_str()); }
|
||||
std::string Format(const std::string &str) { return Format(str.c_str()); }
|
||||
|
||||
std::string FormatOperator(const char *str) const { return Format(std::string("* ") + str); }
|
||||
std::string FormatOperator(const char *str) { return Format(std::string("* ") + str); }
|
||||
|
||||
std::string FormatRelativeTime(uint64_t num_cycles) const {
|
||||
return fmt::format("{: 10.6f} %", RelativeTime(num_cycles) * 100);
|
||||
std::string FormatRelativeTime(unsigned long long num_cycles) {
|
||||
return fmt::format("{: 10.6f} %", RelativeTime(num_cycles, total_cycles_) * 100);
|
||||
}
|
||||
|
||||
std::string FormatAbsoluteTime(uint64_t num_cycles) const {
|
||||
return fmt::format("{: 10.6f} ms", AbsoluteTime(num_cycles));
|
||||
std::string FormatAbsoluteTime(unsigned long long num_cycles) {
|
||||
return fmt::format("{: 10.6f} ms", AbsoluteTime(num_cycles, total_cycles_, total_time_));
|
||||
}
|
||||
|
||||
int64_t depth_{0};
|
||||
std::vector<std::vector<TypedValue>> rows_;
|
||||
uint64_t total_cycles_;
|
||||
unsigned long long total_cycles_;
|
||||
std::chrono::duration<double> total_time_;
|
||||
};
|
||||
|
||||
@@ -150,7 +126,7 @@ class ProfilingStatsToJsonHelper {
|
||||
using json = nlohmann::json;
|
||||
|
||||
public:
|
||||
ProfilingStatsToJsonHelper(uint64_t total_cycles, std::chrono::duration<double> total_time)
|
||||
ProfilingStatsToJsonHelper(unsigned long long total_cycles, std::chrono::duration<double> total_time)
|
||||
: total_cycles_(total_cycles), total_time_(total_time) {}
|
||||
|
||||
void Output(const ProfilingStats &cumulative_stats) { return Output(cumulative_stats, &json_); }
|
||||
@@ -175,7 +151,7 @@ class ProfilingStatsToJsonHelper {
|
||||
}
|
||||
|
||||
json json_;
|
||||
uint64_t total_cycles_;
|
||||
unsigned long long total_cycles_;
|
||||
std::chrono::duration<double> total_time_;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,16 +26,10 @@ namespace plan {
|
||||
* Stores profiling statistics for a single logical operator.
|
||||
*/
|
||||
struct ProfilingStats {
|
||||
static constexpr std::string_view kNumCycles{"num_cycles"};
|
||||
static constexpr std::string_view kRelativeTime{"relative_time"};
|
||||
static constexpr std::string_view kAbsoluteTime{"absolute_time"};
|
||||
static constexpr std::string_view kActualHits{"actual_hits"};
|
||||
|
||||
int64_t actual_hits{0};
|
||||
uint64_t num_cycles{0};
|
||||
unsigned long long num_cycles{0};
|
||||
uint64_t key{0};
|
||||
const char *name{nullptr};
|
||||
nlohmann::json custom_data;
|
||||
// TODO: This should use the allocator for query execution
|
||||
std::vector<ProfilingStats> children;
|
||||
};
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/plan/profile.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
@@ -22,43 +20,6 @@
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
|
||||
class ScopedCustomProfile {
|
||||
public:
|
||||
explicit ScopedCustomProfile(const std::string_view custom_data_name, ExecutionContext &context)
|
||||
: custom_data_name_(custom_data_name), start_time_{utils::ReadTSC()}, context_{&context} {}
|
||||
|
||||
ScopedCustomProfile(const ScopedCustomProfile &) = delete;
|
||||
ScopedCustomProfile(ScopedCustomProfile &&) = delete;
|
||||
ScopedCustomProfile &operator=(const ScopedCustomProfile &) = delete;
|
||||
ScopedCustomProfile &operator=(ScopedCustomProfile &&) = delete;
|
||||
|
||||
// If an exception is thrown in any of these functions that signals a problem that is much bigger than we could handle
|
||||
// it here, thus we don't attempt to handle it.
|
||||
// NOLINTNEXTLINE(bugprone-exception-escape)
|
||||
~ScopedCustomProfile() {
|
||||
if (nullptr != context_->stats_root) [[unlikely]] {
|
||||
auto &custom_data = context_->stats_root->custom_data[custom_data_name_];
|
||||
if (!custom_data.is_object()) {
|
||||
custom_data = nlohmann::json::object();
|
||||
}
|
||||
const auto elapsed = utils::ReadTSC() - start_time_;
|
||||
IncreaseCustomData(custom_data, ProfilingStats::kNumCycles, elapsed);
|
||||
IncreaseCustomData(custom_data, ProfilingStats::kActualHits, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void IncreaseCustomData(nlohmann::json &custom_data, const std::string_view key, const uint64_t increment) {
|
||||
auto &json_data = custom_data[key];
|
||||
const auto numerical_data = json_data.is_null() ? 0 : json_data.get<uint64_t>();
|
||||
json_data = numerical_data + increment;
|
||||
}
|
||||
|
||||
std::string_view custom_data_name_;
|
||||
uint64_t start_time_;
|
||||
ExecutionContext *context_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A RAII class used for profiling logical operators. Instances of this class
|
||||
* update the profiling data stored within the `ExecutionContext` object and build
|
||||
@@ -68,7 +29,7 @@ class ScopedCustomProfile {
|
||||
class ScopedProfile {
|
||||
public:
|
||||
ScopedProfile(uint64_t key, const char *name, query::v2::ExecutionContext *context) noexcept : context_(context) {
|
||||
if (IsProfiling()) [[unlikely]] {
|
||||
if (UNLIKELY(context_->is_profile_query)) {
|
||||
root_ = context_->stats_root;
|
||||
|
||||
// Are we the root logical operator?
|
||||
@@ -99,13 +60,8 @@ class ScopedProfile {
|
||||
}
|
||||
}
|
||||
|
||||
ScopedProfile(const ScopedProfile &) = delete;
|
||||
ScopedProfile(ScopedProfile &&) = delete;
|
||||
ScopedProfile &operator=(const ScopedProfile &) = delete;
|
||||
ScopedProfile &operator=(ScopedProfile &&) = delete;
|
||||
|
||||
~ScopedProfile() noexcept {
|
||||
if (IsProfiling()) [[unlikely]] {
|
||||
if (UNLIKELY(context_->is_profile_query)) {
|
||||
stats_->num_cycles += utils::ReadTSC() - start_time_;
|
||||
|
||||
// Restore the old root ("pop")
|
||||
@@ -114,12 +70,10 @@ class ScopedProfile {
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool IsProfiling() const { return context_->is_profile_query; }
|
||||
|
||||
query::v2::ExecutionContext *context_;
|
||||
ProfilingStats *root_{nullptr};
|
||||
ProfilingStats *stats_{nullptr};
|
||||
uint64_t start_time_{0};
|
||||
unsigned long long start_time_{0};
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2::plan
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
@@ -28,37 +28,110 @@ namespace memgraph::query::v2::plan {
|
||||
template <class TDbAccessor>
|
||||
class VertexCountCache {
|
||||
public:
|
||||
explicit VertexCountCache(TDbAccessor *shard_request_manager) : shard_request_manager_{shard_request_manager} {}
|
||||
VertexCountCache(TDbAccessor *db) : db_(db) {}
|
||||
|
||||
auto NameToLabel(const std::string &name) { return shard_request_manager_->NameToLabel(name); }
|
||||
auto NameToProperty(const std::string &name) { return shard_request_manager_->NameToProperty(name); }
|
||||
auto NameToEdgeType(const std::string &name) { return shard_request_manager_->NameToEdgeType(name); }
|
||||
auto NameToLabel(const std::string &name) { return db_->NameToLabel(name); }
|
||||
auto NameToProperty(const std::string &name) { return db_->NameToProperty(name); }
|
||||
auto NameToEdgeType(const std::string &name) { return db_->NameToEdgeType(name); }
|
||||
|
||||
int64_t VerticesCount() { return 1; }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId /*label*/) { return 1; }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/) { return 1; }
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/,
|
||||
const storage::v3::PropertyValue & /*value*/) {
|
||||
return 1;
|
||||
int64_t VerticesCount() {
|
||||
if (!vertices_count_) vertices_count_ = db_->VerticesCount();
|
||||
return *vertices_count_;
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId /*label*/, storage::v3::PropertyId /*property*/,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> & /*lower*/,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> & /*upper*/) {
|
||||
return 1;
|
||||
int64_t VerticesCount(storage::v3::LabelId label) {
|
||||
if (label_vertex_count_.find(label) == label_vertex_count_.end())
|
||||
label_vertex_count_[label] = db_->VerticesCount(label);
|
||||
return label_vertex_count_.at(label);
|
||||
}
|
||||
|
||||
// For now return true if label is primary label
|
||||
bool LabelIndexExists(storage::v3::LabelId label) { return shard_request_manager_->IsPrimaryLabel(label); }
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property) {
|
||||
auto key = std::make_pair(label, property);
|
||||
if (label_property_vertex_count_.find(key) == label_property_vertex_count_.end())
|
||||
label_property_vertex_count_[key] = db_->VerticesCount(label, property);
|
||||
return label_property_vertex_count_.at(key);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
auto label_prop = std::make_pair(label, property);
|
||||
auto &value_vertex_count = property_value_vertex_count_[label_prop];
|
||||
// TODO: Why do we even need TypedValue in this whole file?
|
||||
auto tv_value(storage::v3::PropertyToTypedValue<TypedValue>(value));
|
||||
if (value_vertex_count.find(tv_value) == value_vertex_count.end())
|
||||
value_vertex_count[tv_value] = db_->VerticesCount(label, property, value);
|
||||
return value_vertex_count.at(tv_value);
|
||||
}
|
||||
|
||||
int64_t VerticesCount(storage::v3::LabelId label, storage::v3::PropertyId property,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<storage::v3::PropertyValue>> &upper) {
|
||||
auto label_prop = std::make_pair(label, property);
|
||||
auto &bounds_vertex_count = property_bounds_vertex_count_[label_prop];
|
||||
BoundsKey bounds = std::make_pair(lower, upper);
|
||||
if (bounds_vertex_count.find(bounds) == bounds_vertex_count.end())
|
||||
bounds_vertex_count[bounds] = db_->VerticesCount(label, property, lower, upper);
|
||||
return bounds_vertex_count.at(bounds);
|
||||
}
|
||||
|
||||
bool LabelIndexExists(storage::v3::LabelId label) { return db_->LabelIndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(storage::v3::LabelId label, storage::v3::PropertyId property) {
|
||||
return shard_request_manager_->IsPrimaryKey(label, property);
|
||||
return db_->LabelPropertyIndexExists(label, property);
|
||||
}
|
||||
|
||||
msgs::ShardRequestManagerInterface *shard_request_manager_;
|
||||
private:
|
||||
typedef std::pair<storage::v3::LabelId, storage::v3::PropertyId> LabelPropertyKey;
|
||||
|
||||
struct LabelPropertyHash {
|
||||
size_t operator()(const LabelPropertyKey &key) const {
|
||||
return utils::HashCombine<storage::v3::LabelId, storage::v3::PropertyId>{}(key.first, key.second);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::pair<std::optional<utils::Bound<storage::v3::PropertyValue>>,
|
||||
std::optional<utils::Bound<storage::v3::PropertyValue>>>
|
||||
BoundsKey;
|
||||
|
||||
struct BoundsHash {
|
||||
size_t operator()(const BoundsKey &key) const {
|
||||
const auto &maybe_lower = key.first;
|
||||
const auto &maybe_upper = key.second;
|
||||
query::v2::TypedValue lower;
|
||||
query::v2::TypedValue upper;
|
||||
if (maybe_lower) lower = storage::v3::PropertyToTypedValue<TypedValue>(maybe_lower->value());
|
||||
if (maybe_upper) upper = storage::v3::PropertyToTypedValue<TypedValue>(maybe_upper->value());
|
||||
query::v2::TypedValue::Hash hash;
|
||||
return utils::HashCombine<size_t, size_t>{}(hash(lower), hash(upper));
|
||||
}
|
||||
};
|
||||
|
||||
struct BoundsEqual {
|
||||
bool operator()(const BoundsKey &a, const BoundsKey &b) const {
|
||||
auto bound_equal = [](const auto &maybe_bound_a, const auto &maybe_bound_b) {
|
||||
if (maybe_bound_a && maybe_bound_b && maybe_bound_a->type() != maybe_bound_b->type()) return false;
|
||||
query::v2::TypedValue bound_a;
|
||||
query::v2::TypedValue bound_b;
|
||||
if (maybe_bound_a) bound_a = storage::v3::PropertyToTypedValue<TypedValue>(maybe_bound_a->value());
|
||||
if (maybe_bound_b) bound_b = storage::v3::PropertyToTypedValue<TypedValue>(maybe_bound_b->value());
|
||||
return query::v2::TypedValue::BoolEqual{}(bound_a, bound_b);
|
||||
};
|
||||
return bound_equal(a.first, b.first) && bound_equal(a.second, b.second);
|
||||
}
|
||||
};
|
||||
|
||||
TDbAccessor *db_;
|
||||
std::optional<int64_t> vertices_count_;
|
||||
std::unordered_map<storage::v3::LabelId, int64_t> label_vertex_count_;
|
||||
std::unordered_map<LabelPropertyKey, int64_t, LabelPropertyHash> label_property_vertex_count_;
|
||||
std::unordered_map<
|
||||
LabelPropertyKey,
|
||||
std::unordered_map<query::v2::TypedValue, int64_t, query::v2::TypedValue::Hash, query::v2::TypedValue::BoolEqual>,
|
||||
LabelPropertyHash>
|
||||
property_value_vertex_count_;
|
||||
std::unordered_map<LabelPropertyKey, std::unordered_map<BoundsKey, int64_t, BoundsHash, BoundsEqual>,
|
||||
LabelPropertyHash>
|
||||
property_bounds_vertex_count_;
|
||||
};
|
||||
|
||||
template <class TDbAccessor>
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "expr/ast/pretty_print_ast_to_original_expression.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {} // namespace memgraph::storage::v3
|
||||
namespace memgraph::query::v2::procedure {
|
||||
class CypherType;
|
||||
using CypherTypePtr = std::unique_ptr<CypherType, std::function<void(CypherType *)>>;
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
293
src/query/v2/procedure/cypher_types.hpp
Normal file
293
src/query/v2/procedure/cypher_types.hpp
Normal file
@@ -0,0 +1,293 @@
|
||||
// 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.
|
||||
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include "mg_procedure.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/procedure/cypher_type_ptr.hpp"
|
||||
#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/pmr/string.hpp"
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
|
||||
class ListType;
|
||||
class NullableType;
|
||||
|
||||
/// Interface for all supported types in openCypher type system.
|
||||
class CypherType {
|
||||
public:
|
||||
CypherType() = default;
|
||||
virtual ~CypherType() = default;
|
||||
|
||||
CypherType(const CypherType &) = delete;
|
||||
CypherType(CypherType &&) = delete;
|
||||
CypherType &operator=(const CypherType &) = delete;
|
||||
CypherType &operator=(CypherType &&) = delete;
|
||||
|
||||
/// Get name of the type as it should be presented to the user.
|
||||
virtual std::string_view GetPresentableName() const = 0;
|
||||
|
||||
/// Return true if given mgp_value is of the type as described by `this`.
|
||||
virtual bool SatisfiesType(const mgp_value &) const = 0;
|
||||
|
||||
/// Return true if given TypedValue is of the type as described by `this`.
|
||||
virtual bool SatisfiesType(const query::v2::TypedValue &) const = 0;
|
||||
|
||||
// The following methods are a simple replacement for RTTI because we have
|
||||
// some special cases we need to handle.
|
||||
virtual const ListType *AsListType() const { return nullptr; }
|
||||
virtual const NullableType *AsNullableType() const { return nullptr; }
|
||||
};
|
||||
|
||||
// Simple Types
|
||||
|
||||
class AnyType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "ANY"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type != MGP_VALUE_TYPE_NULL; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return !value.IsNull(); }
|
||||
};
|
||||
|
||||
class BoolType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "BOOLEAN"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_BOOL; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsBool(); }
|
||||
};
|
||||
|
||||
class StringType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "STRING"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_STRING; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsString(); }
|
||||
};
|
||||
|
||||
class IntType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "INTEGER"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_INT; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsInt(); }
|
||||
};
|
||||
|
||||
class FloatType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "FLOAT"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DOUBLE; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsDouble(); }
|
||||
};
|
||||
|
||||
class NumberType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "NUMBER"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override {
|
||||
return value.type == MGP_VALUE_TYPE_INT || value.type == MGP_VALUE_TYPE_DOUBLE;
|
||||
}
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsInt() || value.IsDouble(); }
|
||||
};
|
||||
|
||||
class NodeType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "NODE"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_VERTEX; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsVertex(); }
|
||||
};
|
||||
|
||||
class RelationshipType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "RELATIONSHIP"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_EDGE; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsEdge(); }
|
||||
};
|
||||
|
||||
class PathType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "PATH"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_PATH; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsPath(); }
|
||||
};
|
||||
|
||||
// You'd think that MapType would be a composite type like ListType, but nope.
|
||||
// Why? No-one really knows. It's defined like that in "CIP2015-09-16 Public
|
||||
// Type System and Type Annotations"
|
||||
// Additionally, MapType also covers NodeType and RelationshipType because
|
||||
// values of that type have property *maps*.
|
||||
class MapType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "MAP"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override {
|
||||
return value.type == MGP_VALUE_TYPE_MAP || value.type == MGP_VALUE_TYPE_VERTEX || value.type == MGP_VALUE_TYPE_EDGE;
|
||||
}
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override {
|
||||
return value.IsMap() || value.IsVertex() || value.IsEdge();
|
||||
}
|
||||
};
|
||||
|
||||
// Temporal Types
|
||||
|
||||
class DateType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "DATE"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DATE; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsDate(); }
|
||||
};
|
||||
|
||||
class LocalTimeType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "LOCAL_TIME"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_LOCAL_TIME; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsLocalTime(); }
|
||||
};
|
||||
|
||||
class LocalDateTimeType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "LOCAL_DATE_TIME"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_LOCAL_DATE_TIME; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsLocalDateTime(); }
|
||||
};
|
||||
|
||||
class DurationType : public CypherType {
|
||||
public:
|
||||
std::string_view GetPresentableName() const override { return "DURATION"; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override { return value.type == MGP_VALUE_TYPE_DURATION; }
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override { return value.IsDuration(); }
|
||||
};
|
||||
|
||||
// Composite Types
|
||||
|
||||
class ListType : public CypherType {
|
||||
public:
|
||||
CypherTypePtr element_type_;
|
||||
utils::pmr::string presentable_name_;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
explicit ListType(CypherTypePtr element_type, utils::MemoryResource *memory)
|
||||
: element_type_(std::move(element_type)), presentable_name_("LIST OF ", memory) {
|
||||
presentable_name_.append(element_type_->GetPresentableName());
|
||||
}
|
||||
|
||||
std::string_view GetPresentableName() const override { return presentable_name_; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override {
|
||||
if (value.type != MGP_VALUE_TYPE_LIST) {
|
||||
return false;
|
||||
}
|
||||
auto *list = value.list_v;
|
||||
const auto list_size = list->elems.size();
|
||||
for (size_t i = 0; i < list_size; ++i) {
|
||||
if (!element_type_->SatisfiesType(list->elems[i])) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override {
|
||||
if (!value.IsList()) return false;
|
||||
for (const auto &elem : value.ValueList()) {
|
||||
if (!element_type_->SatisfiesType(elem)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const ListType *AsListType() const override { return this; }
|
||||
};
|
||||
|
||||
class NullableType : public CypherType {
|
||||
CypherTypePtr type_;
|
||||
utils::pmr::string presentable_name_;
|
||||
|
||||
// Constructor is private, because we use a factory method Create to prevent
|
||||
// nesting NullableType on top of each other.
|
||||
// @throw std::bad_alloc
|
||||
// @throw std::length_error
|
||||
explicit NullableType(CypherTypePtr type, utils::MemoryResource *memory)
|
||||
: type_(std::move(type)), presentable_name_(memory) {
|
||||
const auto *list_type = type_->AsListType();
|
||||
// ListType is specially formatted
|
||||
if (list_type) {
|
||||
presentable_name_.assign("LIST? OF ").append(list_type->element_type_->GetPresentableName());
|
||||
} else {
|
||||
presentable_name_.assign(type_->GetPresentableName()).append("?");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// Create a NullableType of some CypherType.
|
||||
/// If passed in `type` is already a NullableType, it is returned intact.
|
||||
/// Otherwise, `type` is wrapped in a new instance of NullableType.
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
static CypherTypePtr Create(CypherTypePtr type, utils::MemoryResource *memory) {
|
||||
if (type->AsNullableType()) return type;
|
||||
utils::Allocator<NullableType> alloc(memory);
|
||||
auto *nullable = alloc.allocate(1);
|
||||
try {
|
||||
new (nullable) NullableType(std::move(type), memory);
|
||||
} catch (...) {
|
||||
alloc.deallocate(nullable, 1);
|
||||
throw;
|
||||
}
|
||||
return CypherTypePtr(nullable, [alloc](CypherType *base_ptr) mutable {
|
||||
alloc.delete_object(static_cast<NullableType *>(base_ptr));
|
||||
});
|
||||
}
|
||||
|
||||
std::string_view GetPresentableName() const override { return presentable_name_; }
|
||||
|
||||
bool SatisfiesType(const mgp_value &value) const override {
|
||||
return value.type == MGP_VALUE_TYPE_NULL || type_->SatisfiesType(value);
|
||||
}
|
||||
|
||||
bool SatisfiesType(const query::v2::TypedValue &value) const override {
|
||||
return value.IsNull() || type_->SatisfiesType(value);
|
||||
}
|
||||
|
||||
const NullableType *AsNullableType() const override { return this; }
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
36
src/query/v2/procedure/mg_procedure_helpers.cpp
Normal file
36
src/query/v2/procedure/mg_procedure_helpers.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/procedure/mg_procedure_helpers.hpp"
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
MgpUniquePtr<mgp_value> GetStringValueOrSetError(const char *string, mgp_memory *memory, mgp_result *result) {
|
||||
procedure::MgpUniquePtr<mgp_value> value{nullptr, mgp_value_destroy};
|
||||
const auto success =
|
||||
TryOrSetError([&] { return procedure::CreateMgpObject(value, mgp_value_make_string, string, memory); }, result);
|
||||
if (!success) {
|
||||
value.reset();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
69
src/query/v2/procedure/mg_procedure_helpers.hpp
Normal file
69
src/query/v2/procedure/mg_procedure_helpers.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mg_procedure.h"
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
template <typename TResult, typename TFunc, typename... TArgs>
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename TFunc, typename... TArgs>
|
||||
bool CallBool(TFunc func, TArgs... args) {
|
||||
return Call<int>(func, args...) != 0;
|
||||
}
|
||||
|
||||
template <typename TObj>
|
||||
using MgpRawObjectDeleter = void (*)(TObj *);
|
||||
|
||||
template <typename TObj>
|
||||
using MgpUniquePtr = std::unique_ptr<TObj, MgpRawObjectDeleter<TObj>>;
|
||||
|
||||
template <typename TObj, typename TFunc, typename... TArgs>
|
||||
mgp_error CreateMgpObject(MgpUniquePtr<TObj> &obj, TFunc func, TArgs &&...args) {
|
||||
TObj *raw_obj{nullptr};
|
||||
const auto err = func(std::forward<TArgs>(args)..., &raw_obj);
|
||||
obj.reset(raw_obj);
|
||||
return err;
|
||||
}
|
||||
|
||||
template <typename Fun>
|
||||
[[nodiscard]] bool TryOrSetError(Fun &&func, mgp_result *result) {
|
||||
if (const auto err = func(); err == mgp_error::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) {
|
||||
const auto error_msg = fmt::format("Unexpected error ({})!", err);
|
||||
static_cast<void>(mgp_result_set_error_msg(result, error_msg.c_str()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] MgpUniquePtr<mgp_value> GetStringValueOrSetError(const char *string, mgp_memory *memory,
|
||||
mgp_result *result);
|
||||
|
||||
[[nodiscard]] bool InsertResultOrSetError(mgp_result *result, mgp_result_record *record, const char *result_name,
|
||||
mgp_value *value);
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
2812
src/query/v2/procedure/mg_procedure_impl.cpp
Normal file
2812
src/query/v2/procedure/mg_procedure_impl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
927
src/query/v2/procedure/mg_procedure_impl.hpp
Normal file
927
src/query/v2/procedure/mg_procedure_impl.hpp
Normal file
@@ -0,0 +1,927 @@
|
||||
// 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.
|
||||
|
||||
/// @file
|
||||
/// Contains private (implementation) declarations and definitions for
|
||||
/// mg_procedure.h
|
||||
#pragma once
|
||||
|
||||
#include "mg_procedure.h"
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
|
||||
#include "integrations/kafka/consumer.hpp"
|
||||
#include "integrations/pulsar/consumer.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/procedure/cypher_type_ptr.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/pmr/map.hpp"
|
||||
#include "utils/pmr/string.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
/// Wraps memory resource used in custom procedures.
|
||||
///
|
||||
/// This should have been `using mgp_memory = memgraph::utils::MemoryResource`, but that's
|
||||
/// not valid C++ because we have a forward declare `struct mgp_memory` in
|
||||
/// mg_procedure.h
|
||||
/// TODO: Make this extendable in C API, so that custom procedure writer can add
|
||||
/// their own memory management wrappers.
|
||||
struct mgp_memory {
|
||||
memgraph::utils::MemoryResource *impl;
|
||||
};
|
||||
|
||||
/// Immutable container of various values that appear in openCypher.
|
||||
struct mgp_value {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_value>;
|
||||
|
||||
// Construct MGP_VALUE_TYPE_NULL.
|
||||
explicit mgp_value(memgraph::utils::MemoryResource *) noexcept;
|
||||
|
||||
mgp_value(bool, memgraph::utils::MemoryResource *) noexcept;
|
||||
mgp_value(int64_t, memgraph::utils::MemoryResource *) noexcept;
|
||||
mgp_value(double, memgraph::utils::MemoryResource *) noexcept;
|
||||
/// @throw std::bad_alloc
|
||||
mgp_value(const char *, memgraph::utils::MemoryResource *);
|
||||
/// Take ownership of the mgp_list, MemoryResource must match.
|
||||
mgp_value(mgp_list *, memgraph::utils::MemoryResource *) noexcept;
|
||||
/// Take ownership of the mgp_map, MemoryResource must match.
|
||||
mgp_value(mgp_map *, memgraph::utils::MemoryResource *) noexcept;
|
||||
/// Take ownership of the mgp_vertex, MemoryResource must match.
|
||||
mgp_value(mgp_vertex *, memgraph::utils::MemoryResource *) noexcept;
|
||||
/// Take ownership of the mgp_edge, MemoryResource must match.
|
||||
mgp_value(mgp_edge *, memgraph::utils::MemoryResource *) noexcept;
|
||||
/// Take ownership of the mgp_path, MemoryResource must match.
|
||||
mgp_value(mgp_path *, memgraph::utils::MemoryResource *) noexcept;
|
||||
|
||||
mgp_value(mgp_date *, memgraph::utils::MemoryResource *) noexcept;
|
||||
mgp_value(mgp_local_time *, memgraph::utils::MemoryResource *) noexcept;
|
||||
mgp_value(mgp_local_date_time *, memgraph::utils::MemoryResource *) noexcept;
|
||||
mgp_value(mgp_duration *, memgraph::utils::MemoryResource *) noexcept;
|
||||
|
||||
/// Construct by copying memgraph::query::v2::TypedValue using memgraph::utils::MemoryResource.
|
||||
/// mgp_graph is needed to construct mgp_vertex and mgp_edge.
|
||||
/// @throw std::bad_alloc
|
||||
mgp_value(const memgraph::query::v2::TypedValue &, mgp_graph *, memgraph::utils::MemoryResource *);
|
||||
|
||||
/// Construct by copying memgraph::storage::v3::PropertyValue using memgraph::utils::MemoryResource.
|
||||
/// @throw std::bad_alloc
|
||||
mgp_value(const memgraph::storage::v3::PropertyValue &, memgraph::utils::MemoryResource *);
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_value(const mgp_value &) = delete;
|
||||
|
||||
/// Copy construct using given memgraph::utils::MemoryResource.
|
||||
/// @throw std::bad_alloc
|
||||
mgp_value(const mgp_value &, memgraph::utils::MemoryResource *);
|
||||
|
||||
/// Move construct using given memgraph::utils::MemoryResource.
|
||||
/// @throw std::bad_alloc if MemoryResource is different, so we cannot move.
|
||||
mgp_value(mgp_value &&, memgraph::utils::MemoryResource *);
|
||||
|
||||
/// Move construct, memgraph::utils::MemoryResource is inherited.
|
||||
mgp_value(mgp_value &&other) noexcept : mgp_value(other, other.memory) {}
|
||||
|
||||
/// Copy-assignment is not allowed to preserve immutability.
|
||||
mgp_value &operator=(const mgp_value &) = delete;
|
||||
|
||||
/// Move-assignment is not allowed to preserve immutability.
|
||||
mgp_value &operator=(mgp_value &&) = delete;
|
||||
|
||||
~mgp_value() noexcept;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
mgp_value_type type;
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
|
||||
union {
|
||||
bool bool_v;
|
||||
int64_t int_v;
|
||||
double double_v;
|
||||
memgraph::utils::pmr::string string_v;
|
||||
// We use pointers so that taking ownership via C API is easier. Besides,
|
||||
// mgp_map cannot use incomplete mgp_value type, because that would be
|
||||
// undefined behaviour.
|
||||
mgp_list *list_v;
|
||||
mgp_map *map_v;
|
||||
mgp_vertex *vertex_v;
|
||||
mgp_edge *edge_v;
|
||||
mgp_path *path_v;
|
||||
mgp_date *date_v;
|
||||
mgp_local_time *local_time_v;
|
||||
mgp_local_date_time *local_date_time_v;
|
||||
mgp_duration *duration_v;
|
||||
};
|
||||
};
|
||||
|
||||
inline memgraph::utils::DateParameters MapDateParameters(const mgp_date_parameters *parameters) {
|
||||
return {.year = parameters->year, .month = parameters->month, .day = parameters->day};
|
||||
}
|
||||
|
||||
struct mgp_date {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_date>;
|
||||
|
||||
// Hopefully memgraph::utils::Date copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::Date>);
|
||||
|
||||
mgp_date(const memgraph::utils::Date &date, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(date) {}
|
||||
|
||||
mgp_date(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(memgraph::utils::ParseDateParameters(string).first) {}
|
||||
|
||||
mgp_date(const mgp_date_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(MapDateParameters(parameters)) {}
|
||||
|
||||
mgp_date(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(microseconds) {}
|
||||
|
||||
mgp_date(const mgp_date &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(other.date) {}
|
||||
|
||||
mgp_date(mgp_date &&other, memgraph::utils::MemoryResource *memory) noexcept : memory(memory), date(other.date) {}
|
||||
|
||||
mgp_date(mgp_date &&other) noexcept : memory(other.memory), date(other.date) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_date(const mgp_date &) = delete;
|
||||
|
||||
mgp_date &operator=(const mgp_date &) = delete;
|
||||
mgp_date &operator=(mgp_date &&) = delete;
|
||||
|
||||
~mgp_date() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::utils::Date date;
|
||||
};
|
||||
|
||||
inline memgraph::utils::LocalTimeParameters MapLocalTimeParameters(const mgp_local_time_parameters *parameters) {
|
||||
return {.hour = parameters->hour,
|
||||
.minute = parameters->minute,
|
||||
.second = parameters->second,
|
||||
.millisecond = parameters->millisecond,
|
||||
.microsecond = parameters->microsecond};
|
||||
}
|
||||
|
||||
struct mgp_local_time {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_local_time>;
|
||||
|
||||
// Hopefully memgraph::utils::LocalTime copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalTime>);
|
||||
|
||||
mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).first) {}
|
||||
|
||||
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(MapLocalTimeParameters(parameters)) {}
|
||||
|
||||
mgp_local_time(const memgraph::utils::LocalTime &local_time, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(local_time) {}
|
||||
|
||||
mgp_local_time(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(microseconds) {}
|
||||
|
||||
mgp_local_time(const mgp_local_time &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(other.local_time) {}
|
||||
|
||||
mgp_local_time(mgp_local_time &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(other.local_time) {}
|
||||
|
||||
mgp_local_time(mgp_local_time &&other) noexcept : memory(other.memory), local_time(other.local_time) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_local_time(const mgp_local_time &) = delete;
|
||||
|
||||
mgp_local_time &operator=(const mgp_local_time &) = delete;
|
||||
mgp_local_time &operator=(mgp_local_time &&) = delete;
|
||||
|
||||
~mgp_local_time() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::utils::LocalTime local_time;
|
||||
};
|
||||
|
||||
inline memgraph::utils::LocalDateTime CreateLocalDateTimeFromString(const std::string_view string) {
|
||||
const auto &[date_parameters, local_time_parameters] = memgraph::utils::ParseLocalDateTimeParameters(string);
|
||||
return memgraph::utils::LocalDateTime{date_parameters, local_time_parameters};
|
||||
}
|
||||
|
||||
struct mgp_local_date_time {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_local_date_time>;
|
||||
|
||||
// Hopefully memgraph::utils::LocalDateTime copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::LocalDateTime>);
|
||||
|
||||
mgp_local_date_time(const memgraph::utils::LocalDateTime &local_date_time,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_date_time(local_date_time) {}
|
||||
|
||||
mgp_local_date_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_date_time(CreateLocalDateTimeFromString(string)) {}
|
||||
|
||||
mgp_local_date_time(const mgp_local_date_time_parameters *parameters,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory),
|
||||
local_date_time(MapDateParameters(parameters->date_parameters),
|
||||
MapLocalTimeParameters(parameters->local_time_parameters)) {}
|
||||
|
||||
mgp_local_date_time(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_date_time(microseconds) {}
|
||||
|
||||
mgp_local_date_time(const mgp_local_date_time &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_date_time(other.local_date_time) {}
|
||||
|
||||
mgp_local_date_time(mgp_local_date_time &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_date_time(other.local_date_time) {}
|
||||
|
||||
mgp_local_date_time(mgp_local_date_time &&other) noexcept
|
||||
: memory(other.memory), local_date_time(other.local_date_time) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_local_date_time(const mgp_local_date_time &) = delete;
|
||||
|
||||
mgp_local_date_time &operator=(const mgp_local_date_time &) = delete;
|
||||
mgp_local_date_time &operator=(mgp_local_date_time &&) = delete;
|
||||
|
||||
~mgp_local_date_time() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::utils::LocalDateTime local_date_time;
|
||||
};
|
||||
|
||||
inline memgraph::utils::DurationParameters MapDurationParameters(const mgp_duration_parameters *parameters) {
|
||||
return {.day = parameters->day,
|
||||
.hour = parameters->hour,
|
||||
.minute = parameters->minute,
|
||||
.second = parameters->second,
|
||||
.millisecond = parameters->millisecond,
|
||||
.microsecond = parameters->microsecond};
|
||||
}
|
||||
|
||||
struct mgp_duration {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_duration>;
|
||||
|
||||
// Hopefully memgraph::utils::Duration copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::utils::Duration>);
|
||||
|
||||
mgp_duration(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(memgraph::utils::ParseDurationParameters(string)) {}
|
||||
|
||||
mgp_duration(const mgp_duration_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(MapDurationParameters(parameters)) {}
|
||||
|
||||
mgp_duration(const memgraph::utils::Duration &duration, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(duration) {}
|
||||
|
||||
mgp_duration(const int64_t microseconds, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(microseconds) {}
|
||||
|
||||
mgp_duration(const mgp_duration &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(other.duration) {}
|
||||
|
||||
mgp_duration(mgp_duration &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(other.duration) {}
|
||||
|
||||
mgp_duration(mgp_duration &&other) noexcept : memory(other.memory), duration(other.duration) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_duration(const mgp_duration &) = delete;
|
||||
|
||||
mgp_duration &operator=(const mgp_duration &) = delete;
|
||||
mgp_duration &operator=(mgp_duration &&) = delete;
|
||||
|
||||
~mgp_duration() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::utils::Duration duration;
|
||||
};
|
||||
|
||||
struct mgp_list {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_list>;
|
||||
|
||||
explicit mgp_list(memgraph::utils::MemoryResource *memory) : elems(memory) {}
|
||||
|
||||
mgp_list(memgraph::utils::pmr::vector<mgp_value> &&elems, memgraph::utils::MemoryResource *memory)
|
||||
: elems(std::move(elems), memory) {}
|
||||
|
||||
mgp_list(const mgp_list &other, memgraph::utils::MemoryResource *memory) : elems(other.elems, memory) {}
|
||||
|
||||
mgp_list(mgp_list &&other, memgraph::utils::MemoryResource *memory) : elems(std::move(other.elems), memory) {}
|
||||
|
||||
mgp_list(mgp_list &&other) noexcept : elems(std::move(other.elems)) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_list(const mgp_list &) = delete;
|
||||
|
||||
mgp_list &operator=(const mgp_list &) = delete;
|
||||
mgp_list &operator=(mgp_list &&) = delete;
|
||||
|
||||
~mgp_list() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept {
|
||||
return elems.get_allocator().GetMemoryResource();
|
||||
}
|
||||
|
||||
// C++17 vector can work with incomplete type.
|
||||
memgraph::utils::pmr::vector<mgp_value> elems;
|
||||
};
|
||||
|
||||
struct mgp_map {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_map>;
|
||||
|
||||
explicit mgp_map(memgraph::utils::MemoryResource *memory) : items(memory) {}
|
||||
|
||||
mgp_map(memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_value> &&items,
|
||||
memgraph::utils::MemoryResource *memory)
|
||||
: items(std::move(items), memory) {}
|
||||
|
||||
mgp_map(const mgp_map &other, memgraph::utils::MemoryResource *memory) : items(other.items, memory) {}
|
||||
|
||||
mgp_map(mgp_map &&other, memgraph::utils::MemoryResource *memory) : items(std::move(other.items), memory) {}
|
||||
|
||||
mgp_map(mgp_map &&other) noexcept : items(std::move(other.items)) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_map(const mgp_map &) = delete;
|
||||
|
||||
mgp_map &operator=(const mgp_map &) = delete;
|
||||
mgp_map &operator=(mgp_map &&) = delete;
|
||||
|
||||
~mgp_map() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept {
|
||||
return items.get_allocator().GetMemoryResource();
|
||||
}
|
||||
|
||||
// Unfortunately using incomplete type with map is undefined, so mgp_map
|
||||
// needs to be defined after mgp_value.
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_value> items;
|
||||
};
|
||||
|
||||
struct mgp_map_item {
|
||||
const char *key;
|
||||
mgp_value *value;
|
||||
};
|
||||
|
||||
struct mgp_map_items_iterator {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_map_items_iterator>;
|
||||
|
||||
mgp_map_items_iterator(mgp_map *map, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory), map(map), current_it(map->items.begin()) {
|
||||
if (current_it != map->items.end()) {
|
||||
current.key = current_it->first.c_str();
|
||||
current.value = ¤t_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
mgp_map_items_iterator(const mgp_map_items_iterator &) = delete;
|
||||
mgp_map_items_iterator(mgp_map_items_iterator &&) = delete;
|
||||
mgp_map_items_iterator &operator=(const mgp_map_items_iterator &) = delete;
|
||||
mgp_map_items_iterator &operator=(mgp_map_items_iterator &&) = delete;
|
||||
|
||||
~mgp_map_items_iterator() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_map *map;
|
||||
decltype(map->items.begin()) current_it;
|
||||
mgp_map_item current;
|
||||
};
|
||||
|
||||
struct mgp_vertex {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_vertex>;
|
||||
|
||||
// Hopefully VertexAccessor copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::v2::VertexAccessor>);
|
||||
|
||||
mgp_vertex(memgraph::query::v2::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(v), graph(graph) {}
|
||||
|
||||
mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
mgp_vertex(mgp_vertex &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_vertex(const mgp_vertex &) = delete;
|
||||
|
||||
mgp_vertex &operator=(const mgp_vertex &) = delete;
|
||||
mgp_vertex &operator=(mgp_vertex &&) = delete;
|
||||
|
||||
bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; }
|
||||
bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); };
|
||||
|
||||
~mgp_vertex() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::query::v2::VertexAccessor impl;
|
||||
mgp_graph *graph;
|
||||
};
|
||||
|
||||
struct mgp_edge {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
/// We don't actually need this, but it simplifies the C API, because we store
|
||||
/// the allocator which was used to allocate `this`.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_edge>;
|
||||
|
||||
// TODO(antaljanosbenjamin): Handle this static assert failure when we will support procedures again
|
||||
// Hopefully EdgeAccessor copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
// static_assert(std::is_nothrow_copy_constructible_v<memgraph::query::v2::EdgeAccessor>);
|
||||
|
||||
static mgp_edge *Copy(const mgp_edge &edge, mgp_memory &memory);
|
||||
|
||||
mgp_edge(const memgraph::query::v2::EdgeAccessor &impl, mgp_graph *graph,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {}
|
||||
|
||||
mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {}
|
||||
|
||||
mgp_edge(mgp_edge &&other, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(other.memory), impl(other.impl), from(std::move(other.from), memory), to(std::move(other.to), memory) {}
|
||||
|
||||
mgp_edge(mgp_edge &&other) noexcept
|
||||
: memory(other.memory), impl(other.impl), from(std::move(other.from)), to(std::move(other.to)) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_edge(const mgp_edge &) = delete;
|
||||
|
||||
mgp_edge &operator=(const mgp_edge &) = delete;
|
||||
mgp_edge &operator=(mgp_edge &&) = delete;
|
||||
~mgp_edge() = default;
|
||||
|
||||
bool operator==(const mgp_edge &other) const noexcept { return this->impl == other.impl; }
|
||||
bool operator!=(const mgp_edge &other) const noexcept { return !(*this == other); };
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
memgraph::query::v2::EdgeAccessor impl;
|
||||
mgp_vertex from;
|
||||
mgp_vertex to;
|
||||
};
|
||||
|
||||
struct mgp_path {
|
||||
/// Allocator type so that STL containers are aware that we need one.
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_path>;
|
||||
|
||||
explicit mgp_path(memgraph::utils::MemoryResource *memory) : vertices(memory), edges(memory) {}
|
||||
|
||||
mgp_path(const mgp_path &other, memgraph::utils::MemoryResource *memory)
|
||||
: vertices(other.vertices, memory), edges(other.edges, memory) {}
|
||||
|
||||
mgp_path(mgp_path &&other, memgraph::utils::MemoryResource *memory)
|
||||
: vertices(std::move(other.vertices), memory), edges(std::move(other.edges), memory) {}
|
||||
|
||||
mgp_path(mgp_path &&other) noexcept : vertices(std::move(other.vertices)), edges(std::move(other.edges)) {}
|
||||
|
||||
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
|
||||
mgp_path(const mgp_path &) = delete;
|
||||
|
||||
mgp_path &operator=(const mgp_path &) = delete;
|
||||
mgp_path &operator=(mgp_path &&) = delete;
|
||||
|
||||
~mgp_path() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept {
|
||||
return vertices.get_allocator().GetMemoryResource();
|
||||
}
|
||||
|
||||
memgraph::utils::pmr::vector<mgp_vertex> vertices;
|
||||
memgraph::utils::pmr::vector<mgp_edge> edges;
|
||||
};
|
||||
|
||||
struct mgp_result_record {
|
||||
/// Result record signature as defined for mgp_proc.
|
||||
const memgraph::utils::pmr::map<memgraph::utils::pmr::string,
|
||||
std::pair<const memgraph::query::v2::procedure::CypherType *, bool>> *signature;
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string, memgraph::query::v2::TypedValue> values;
|
||||
};
|
||||
|
||||
struct mgp_result {
|
||||
explicit mgp_result(
|
||||
const memgraph::utils::pmr::map<memgraph::utils::pmr::string,
|
||||
std::pair<const memgraph::query::v2::procedure::CypherType *, bool>> *signature,
|
||||
memgraph::utils::MemoryResource *mem)
|
||||
: signature(signature), rows(mem) {}
|
||||
|
||||
/// Result record signature as defined for mgp_proc.
|
||||
const memgraph::utils::pmr::map<memgraph::utils::pmr::string,
|
||||
std::pair<const memgraph::query::v2::procedure::CypherType *, bool>> *signature;
|
||||
memgraph::utils::pmr::vector<mgp_result_record> rows;
|
||||
std::optional<memgraph::utils::pmr::string> error_msg;
|
||||
};
|
||||
|
||||
struct mgp_func_result {
|
||||
mgp_func_result() {}
|
||||
/// Return Magic function result. If user forgets it, the error is raised
|
||||
std::optional<memgraph::query::v2::TypedValue> value;
|
||||
/// Return Magic function result with potential error
|
||||
std::optional<memgraph::utils::pmr::string> error_msg;
|
||||
};
|
||||
|
||||
struct mgp_graph {
|
||||
memgraph::query::v2::DbAccessor *impl;
|
||||
memgraph::storage::v3::View view;
|
||||
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
|
||||
// `ctx` field is out of place here.
|
||||
memgraph::query::v2::ExecutionContext *ctx;
|
||||
|
||||
static mgp_graph WritableGraph(memgraph::query::v2::DbAccessor &acc, memgraph::storage::v3::View view,
|
||||
memgraph::query::v2::ExecutionContext &ctx) {
|
||||
return mgp_graph{&acc, view, &ctx};
|
||||
}
|
||||
|
||||
static mgp_graph NonWritableGraph(memgraph::query::v2::DbAccessor &acc, memgraph::storage::v3::View view) {
|
||||
return mgp_graph{&acc, view, nullptr};
|
||||
}
|
||||
};
|
||||
|
||||
// Prevents user to use ExecutionContext in writable callables
|
||||
struct mgp_func_context {
|
||||
memgraph::query::v2::DbAccessor *impl;
|
||||
memgraph::storage::v3::View view;
|
||||
};
|
||||
struct mgp_properties_iterator {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_properties_iterator>;
|
||||
|
||||
// Define members at the start because we use decltype a lot here, so members
|
||||
// need to be visible in method definitions.
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_graph *graph;
|
||||
std::remove_reference_t<decltype(*std::declval<memgraph::query::v2::VertexAccessor>().Properties(graph->view))> pvs;
|
||||
decltype(pvs.begin()) current_it;
|
||||
std::optional<std::pair<memgraph::utils::pmr::string, mgp_value>> current;
|
||||
mgp_property property{nullptr, nullptr};
|
||||
|
||||
// Construct with no properties.
|
||||
explicit mgp_properties_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory), graph(graph), current_it(pvs.begin()) {}
|
||||
|
||||
// May throw who the #$@! knows what because PropertyValueStore doesn't
|
||||
// document what it throws, and it may surely throw some piece of !@#$
|
||||
// exception because it's built on top of STL and other libraries.
|
||||
mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) {
|
||||
if (current_it != this->pvs.end()) {
|
||||
current.emplace(memgraph::utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory),
|
||||
mgp_value(current_it->second, memory));
|
||||
property.name = current->first.c_str();
|
||||
property.value = ¤t->second;
|
||||
}
|
||||
}
|
||||
|
||||
mgp_properties_iterator(const mgp_properties_iterator &) = delete;
|
||||
mgp_properties_iterator(mgp_properties_iterator &&) = delete;
|
||||
|
||||
mgp_properties_iterator &operator=(const mgp_properties_iterator &) = delete;
|
||||
mgp_properties_iterator &operator=(mgp_properties_iterator &&) = delete;
|
||||
|
||||
~mgp_properties_iterator() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }
|
||||
};
|
||||
|
||||
struct mgp_edges_iterator {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>;
|
||||
|
||||
// Hopefully mgp_vertex copy constructor remains noexcept, so that we can
|
||||
// have everything noexcept here.
|
||||
static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>);
|
||||
|
||||
mgp_edges_iterator(const mgp_vertex &v, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), source_vertex(v, memory) {}
|
||||
|
||||
mgp_edges_iterator(mgp_edges_iterator &&other) noexcept
|
||||
: memory(other.memory),
|
||||
source_vertex(std::move(other.source_vertex)),
|
||||
in(std::move(other.in)),
|
||||
in_it(std::move(other.in_it)),
|
||||
out(std::move(other.out)),
|
||||
out_it(std::move(other.out_it)),
|
||||
current_e(std::move(other.current_e)) {}
|
||||
|
||||
mgp_edges_iterator(const mgp_edges_iterator &) = delete;
|
||||
mgp_edges_iterator &operator=(const mgp_edges_iterator &) = delete;
|
||||
mgp_edges_iterator &operator=(mgp_edges_iterator &&) = delete;
|
||||
|
||||
~mgp_edges_iterator() = default;
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_vertex source_vertex;
|
||||
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.InEdges(source_vertex.graph->view))>> in;
|
||||
std::optional<decltype(in->begin())> in_it;
|
||||
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.OutEdges(source_vertex.graph->view))>> out;
|
||||
std::optional<decltype(out->begin())> out_it;
|
||||
std::optional<mgp_edge> current_e;
|
||||
};
|
||||
|
||||
struct mgp_vertices_iterator {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_vertices_iterator>;
|
||||
|
||||
/// @throw anything VerticesIterable may throw
|
||||
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
|
||||
: memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) {
|
||||
if (current_it != vertices.end()) {
|
||||
current_v.emplace(*current_it, graph, memory);
|
||||
}
|
||||
}
|
||||
|
||||
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }
|
||||
|
||||
memgraph::utils::MemoryResource *memory;
|
||||
mgp_graph *graph;
|
||||
decltype(graph->impl->Vertices(graph->view)) vertices;
|
||||
decltype(vertices.begin()) current_it;
|
||||
std::optional<mgp_vertex> current_v;
|
||||
};
|
||||
|
||||
struct mgp_type {
|
||||
memgraph::query::v2::procedure::CypherTypePtr impl;
|
||||
};
|
||||
|
||||
struct ProcedureInfo {
|
||||
bool is_write = false;
|
||||
std::optional<memgraph::query::v2::AuthQuery::Privilege> required_privilege = std::nullopt;
|
||||
};
|
||||
struct mgp_proc {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_proc>;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const char *name, mgp_proc_cb cb, memgraph::utils::MemoryResource *memory, const ProcedureInfo &info = {})
|
||||
: name(name, memory), cb(cb), args(memory), opt_args(memory), results(memory), info(info) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const char *name, std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
|
||||
memgraph::utils::MemoryResource *memory, const ProcedureInfo &info = {})
|
||||
: name(name, memory), cb(cb), args(memory), opt_args(memory), results(memory), info(info) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const std::string_view name, std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
|
||||
memgraph::utils::MemoryResource *memory, const ProcedureInfo &info = {})
|
||||
: name(name, memory), cb(cb), args(memory), opt_args(memory), results(memory), info(info) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const mgp_proc &other, memgraph::utils::MemoryResource *memory)
|
||||
: name(other.name, memory),
|
||||
cb(other.cb),
|
||||
args(other.args, memory),
|
||||
opt_args(other.opt_args, memory),
|
||||
results(other.results, memory),
|
||||
info(other.info) {}
|
||||
|
||||
mgp_proc(mgp_proc &&other, memgraph::utils::MemoryResource *memory)
|
||||
: name(std::move(other.name), memory),
|
||||
cb(std::move(other.cb)),
|
||||
args(std::move(other.args), memory),
|
||||
opt_args(std::move(other.opt_args), memory),
|
||||
results(std::move(other.results), memory),
|
||||
info(other.info) {}
|
||||
|
||||
mgp_proc(const mgp_proc &other) = default;
|
||||
mgp_proc(mgp_proc &&other) = default;
|
||||
|
||||
mgp_proc &operator=(const mgp_proc &) = delete;
|
||||
mgp_proc &operator=(mgp_proc &&) = delete;
|
||||
|
||||
~mgp_proc() = default;
|
||||
|
||||
/// Name of the procedure.
|
||||
memgraph::utils::pmr::string name;
|
||||
/// Entry-point for the procedure.
|
||||
std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb;
|
||||
/// Required, positional arguments as a (name, type) pair.
|
||||
memgraph::utils::pmr::vector<
|
||||
std::pair<memgraph::utils::pmr::string, const memgraph::query::v2::procedure::CypherType *>>
|
||||
args;
|
||||
/// Optional positional arguments as a (name, type, default_value) tuple.
|
||||
memgraph::utils::pmr::vector<
|
||||
std::tuple<memgraph::utils::pmr::string, const memgraph::query::v2::procedure::CypherType *,
|
||||
memgraph::query::v2::TypedValue>>
|
||||
opt_args;
|
||||
/// Fields this procedure returns, as a (name -> (type, is_deprecated)) map.
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string,
|
||||
std::pair<const memgraph::query::v2::procedure::CypherType *, bool>>
|
||||
results;
|
||||
ProcedureInfo info;
|
||||
};
|
||||
|
||||
struct mgp_trans {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_trans>;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_trans(const char *name, mgp_trans_cb cb, memgraph::utils::MemoryResource *memory)
|
||||
: name(name, memory), cb(cb), results(memory) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_trans(const char *name, std::function<void(mgp_messages *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
|
||||
memgraph::utils::MemoryResource *memory)
|
||||
: name(name, memory), cb(cb), results(memory) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_trans(const mgp_trans &other, memgraph::utils::MemoryResource *memory)
|
||||
: name(other.name, memory), cb(other.cb), results(other.results) {}
|
||||
|
||||
mgp_trans(mgp_trans &&other, memgraph::utils::MemoryResource *memory)
|
||||
: name(std::move(other.name), memory), cb(std::move(other.cb)), results(std::move(other.results)) {}
|
||||
|
||||
mgp_trans(const mgp_trans &other) = default;
|
||||
mgp_trans(mgp_trans &&other) = default;
|
||||
|
||||
mgp_trans &operator=(const mgp_trans &) = delete;
|
||||
mgp_trans &operator=(mgp_trans &&) = delete;
|
||||
|
||||
~mgp_trans() = default;
|
||||
|
||||
/// Name of the transformation.
|
||||
memgraph::utils::pmr::string name;
|
||||
/// Entry-point for the transformation.
|
||||
std::function<void(mgp_messages *, mgp_graph *, mgp_result *, mgp_memory *)> cb;
|
||||
/// Fields this transformation returns.
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string,
|
||||
std::pair<const memgraph::query::v2::procedure::CypherType *, bool>>
|
||||
results;
|
||||
};
|
||||
|
||||
struct mgp_func {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_func>;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_func(const char *name, mgp_func_cb cb, memgraph::utils::MemoryResource *memory)
|
||||
: name(name, memory), cb(cb), args(memory), opt_args(memory) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_func(const char *name, std::function<void(mgp_list *, mgp_func_context *, mgp_func_result *, mgp_memory *)> cb,
|
||||
memgraph::utils::MemoryResource *memory)
|
||||
: name(name, memory), cb(cb), args(memory), opt_args(memory) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_func(const mgp_func &other, memgraph::utils::MemoryResource *memory)
|
||||
: name(other.name, memory), cb(other.cb), args(other.args, memory), opt_args(other.opt_args, memory) {}
|
||||
|
||||
mgp_func(mgp_func &&other, memgraph::utils::MemoryResource *memory)
|
||||
: name(std::move(other.name), memory),
|
||||
cb(std::move(other.cb)),
|
||||
args(std::move(other.args), memory),
|
||||
opt_args(std::move(other.opt_args), memory) {}
|
||||
|
||||
mgp_func(const mgp_func &other) = default;
|
||||
mgp_func(mgp_func &&other) = default;
|
||||
|
||||
mgp_func &operator=(const mgp_func &) = delete;
|
||||
mgp_func &operator=(mgp_func &&) = delete;
|
||||
|
||||
~mgp_func() = default;
|
||||
|
||||
/// Name of the function.
|
||||
memgraph::utils::pmr::string name;
|
||||
/// Entry-point for the function.
|
||||
std::function<void(mgp_list *, mgp_func_context *, mgp_func_result *, mgp_memory *)> cb;
|
||||
/// Required, positional arguments as a (name, type) pair.
|
||||
memgraph::utils::pmr::vector<
|
||||
std::pair<memgraph::utils::pmr::string, const memgraph::query::v2::procedure::CypherType *>>
|
||||
args;
|
||||
/// Optional positional arguments as a (name, type, default_value) tuple.
|
||||
memgraph::utils::pmr::vector<
|
||||
std::tuple<memgraph::utils::pmr::string, const memgraph::query::v2::procedure::CypherType *,
|
||||
memgraph::query::v2::TypedValue>>
|
||||
opt_args;
|
||||
};
|
||||
|
||||
mgp_error MgpTransAddFixedResult(mgp_trans *trans) noexcept;
|
||||
|
||||
struct mgp_module {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_module>;
|
||||
|
||||
explicit mgp_module(memgraph::utils::MemoryResource *memory)
|
||||
: procedures(memory), transformations(memory), functions(memory) {}
|
||||
|
||||
mgp_module(const mgp_module &other, memgraph::utils::MemoryResource *memory)
|
||||
: procedures(other.procedures, memory),
|
||||
transformations(other.transformations, memory),
|
||||
functions(other.functions, memory) {}
|
||||
|
||||
mgp_module(mgp_module &&other, memgraph::utils::MemoryResource *memory)
|
||||
: procedures(std::move(other.procedures), memory),
|
||||
transformations(std::move(other.transformations), memory),
|
||||
functions(std::move(other.functions), memory) {}
|
||||
|
||||
mgp_module(const mgp_module &) = default;
|
||||
mgp_module(mgp_module &&) = default;
|
||||
|
||||
mgp_module &operator=(const mgp_module &) = delete;
|
||||
mgp_module &operator=(mgp_module &&) = delete;
|
||||
|
||||
~mgp_module() = default;
|
||||
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_proc> procedures;
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_trans> transformations;
|
||||
memgraph::utils::pmr::map<memgraph::utils::pmr::string, mgp_func> functions;
|
||||
};
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
/// @throw anything std::ostream::operator<< may throw.
|
||||
void PrintProcSignature(const mgp_proc &, std::ostream *);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
/// @throw anything std::ostream::operator<< may throw.
|
||||
void PrintFuncSignature(const mgp_func &, std::ostream &);
|
||||
|
||||
bool IsValidIdentifierName(const char *name);
|
||||
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
|
||||
struct mgp_message {
|
||||
explicit mgp_message(const memgraph::integrations::kafka::Message &message) : msg{&message} {}
|
||||
explicit mgp_message(const memgraph::integrations::pulsar::Message &message) : msg{message} {}
|
||||
|
||||
using KafkaMessage = const memgraph::integrations::kafka::Message *;
|
||||
using PulsarMessage = memgraph::integrations::pulsar::Message;
|
||||
std::variant<KafkaMessage, PulsarMessage> msg;
|
||||
};
|
||||
|
||||
struct mgp_messages {
|
||||
using allocator_type = memgraph::utils::Allocator<mgp_messages>;
|
||||
using storage_type = memgraph::utils::pmr::vector<mgp_message>;
|
||||
explicit mgp_messages(storage_type &&storage) : messages(std::move(storage)) {}
|
||||
|
||||
mgp_messages(const mgp_messages &) = delete;
|
||||
mgp_messages &operator=(const mgp_messages &) = delete;
|
||||
|
||||
mgp_messages(mgp_messages &&) = delete;
|
||||
mgp_messages &operator=(mgp_messages &&) = delete;
|
||||
|
||||
~mgp_messages() = default;
|
||||
|
||||
storage_type messages;
|
||||
};
|
||||
|
||||
memgraph::query::v2::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils::MemoryResource *memory);
|
||||
1258
src/query/v2/procedure/module.cpp
Normal file
1258
src/query/v2/procedure/module.cpp
Normal file
File diff suppressed because it is too large
Load Diff
246
src/query/v2/procedure/module.hpp
Normal file
246
src/query/v2/procedure/module.hpp
Normal file
@@ -0,0 +1,246 @@
|
||||
// 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.
|
||||
|
||||
/// @file
|
||||
/// API for loading and registering modules providing custom oC procedures
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "query/v2/procedure/cypher_types.hpp"
|
||||
#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
|
||||
class CypherMainVisitorTest;
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
|
||||
class Module {
|
||||
public:
|
||||
Module() {}
|
||||
virtual ~Module();
|
||||
Module(const Module &) = delete;
|
||||
Module(Module &&) = delete;
|
||||
Module &operator=(const Module &) = delete;
|
||||
Module &operator=(Module &&) = delete;
|
||||
|
||||
/// Invokes the (optional) shutdown function and closes the module.
|
||||
virtual bool Close() = 0;
|
||||
|
||||
/// Returns registered procedures of this module
|
||||
virtual const std::map<std::string, mgp_proc, std::less<>> *Procedures() const = 0;
|
||||
/// Returns registered transformations of this module
|
||||
virtual const std::map<std::string, mgp_trans, std::less<>> *Transformations() const = 0;
|
||||
// /// Returns registered functions of this module
|
||||
virtual const std::map<std::string, mgp_func, std::less<>> *Functions() const = 0;
|
||||
|
||||
virtual std::optional<std::filesystem::path> Path() const = 0;
|
||||
};
|
||||
|
||||
/// Proxy for a registered Module, acquires a read lock from ModuleRegistry.
|
||||
class ModulePtr final {
|
||||
const Module *module_{nullptr};
|
||||
std::shared_lock<utils::RWLock> lock_;
|
||||
|
||||
public:
|
||||
ModulePtr() = default;
|
||||
ModulePtr(std::nullptr_t) {}
|
||||
ModulePtr(const Module *module, std::shared_lock<utils::RWLock> lock) : module_(module), lock_(std::move(lock)) {}
|
||||
|
||||
explicit operator bool() const { return static_cast<bool>(module_); }
|
||||
|
||||
const Module &operator*() const { return *module_; }
|
||||
const Module *operator->() const { return module_; }
|
||||
};
|
||||
|
||||
/// Thread-safe registration of modules from libraries, uses utils::RWLock.
|
||||
class ModuleRegistry final {
|
||||
friend CypherMainVisitorTest;
|
||||
|
||||
std::map<std::string, std::unique_ptr<Module>, std::less<>> modules_;
|
||||
mutable utils::RWLock lock_{utils::RWLock::Priority::WRITE};
|
||||
std::unique_ptr<utils::MemoryResource> shared_{std::make_unique<utils::ResourceWithOutOfMemoryException>()};
|
||||
|
||||
bool RegisterModule(std::string_view name, std::unique_ptr<Module> module);
|
||||
|
||||
void DoUnloadAllModules();
|
||||
|
||||
/// Loads the module if it's in the modules_dir directory
|
||||
/// @return Whether the module was loaded
|
||||
bool LoadModuleIfFound(const std::filesystem::path &modules_dir, std::string_view name);
|
||||
|
||||
void LoadModulesFromDirectory(const std::filesystem::path &modules_dir);
|
||||
|
||||
public:
|
||||
ModuleRegistry();
|
||||
|
||||
/// Set the modules directories that will be used when (re)loading modules.
|
||||
void SetModulesDirectory(std::vector<std::filesystem::path> modules_dir, const std::filesystem::path &data_directory);
|
||||
const std::vector<std::filesystem::path> &GetModulesDirectory() const;
|
||||
|
||||
/// Atomically load or reload a module with a particular name from the given
|
||||
/// directory.
|
||||
///
|
||||
/// Takes a write lock. If the module exists it is reloaded. Otherwise, the
|
||||
/// module is loaded from the file whose filename, without the extension,
|
||||
/// matches the module's name. If multiple such files exist, only one is
|
||||
/// chosen, in an unspecified manner. If loading of the chosen file fails, no
|
||||
/// other files are tried.
|
||||
///
|
||||
/// Return true if the module was loaded or reloaded successfully, false
|
||||
/// otherwise.
|
||||
bool LoadOrReloadModuleFromName(std::string_view name);
|
||||
|
||||
/// Atomically unload all modules and then load all possible modules from the
|
||||
/// set directories.
|
||||
///
|
||||
/// Takes a write lock.
|
||||
void UnloadAndLoadModulesFromDirectories();
|
||||
|
||||
/// Find a module with given name or return nullptr.
|
||||
/// Takes a read lock.
|
||||
ModulePtr GetModuleNamed(std::string_view name) const;
|
||||
|
||||
/// Remove all loaded (non-builtin) modules.
|
||||
/// Takes a write lock.
|
||||
void UnloadAllModules();
|
||||
|
||||
/// Returns the shared memory allocator used by modules
|
||||
utils::MemoryResource &GetSharedMemoryResource() noexcept;
|
||||
|
||||
bool RegisterMgProcedure(std::string_view name, mgp_proc proc);
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
/// Single, global module registry.
|
||||
extern ModuleRegistry gModuleRegistry;
|
||||
|
||||
/// Return the ModulePtr and `mgp_proc *` of the found procedure after resolving
|
||||
/// `fully_qualified_procedure_name`. `memory` is used for temporary allocations
|
||||
/// inside this function. ModulePtr must be kept alive to make sure it won't be
|
||||
/// unloaded.
|
||||
std::optional<std::pair<procedure::ModulePtr, const mgp_proc *>> FindProcedure(
|
||||
const ModuleRegistry &module_registry, std::string_view fully_qualified_procedure_name,
|
||||
utils::MemoryResource *memory);
|
||||
|
||||
/// Return the ModulePtr and `mgp_trans *` of the found transformation after resolving
|
||||
/// `fully_qualified_transformation_name`. `memory` is used for temporary allocations
|
||||
/// inside this function. ModulePtr must be kept alive to make sure it won't be
|
||||
/// unloaded.
|
||||
std::optional<std::pair<procedure::ModulePtr, const mgp_trans *>> FindTransformation(
|
||||
const ModuleRegistry &module_registry, std::string_view fully_qualified_transformation_name,
|
||||
utils::MemoryResource *memory);
|
||||
|
||||
/// Return the ModulePtr and `mgp_func *` of the found function after resolving
|
||||
/// `fully_qualified_function_name` if found. If there is no such function
|
||||
/// std::nullopt is returned. `memory` is used for temporary allocations
|
||||
/// inside this function. ModulePtr must be kept alive to make sure it won't be unloaded.
|
||||
std::optional<std::pair<procedure::ModulePtr, const mgp_func *>> FindFunction(
|
||||
const ModuleRegistry &module_registry, std::string_view fully_qualified_function_name,
|
||||
utils::MemoryResource *memory);
|
||||
|
||||
template <typename T>
|
||||
concept IsCallable = utils::SameAsAnyOf<T, mgp_proc, mgp_func>;
|
||||
|
||||
template <IsCallable TCall>
|
||||
void ConstructArguments(const std::vector<TypedValue> &args, const TCall &callable,
|
||||
const std::string_view fully_qualified_name, mgp_list &args_list, mgp_graph &graph) {
|
||||
const auto n_args = args.size();
|
||||
const auto c_args_sz = callable.args.size();
|
||||
const auto c_opt_args_sz = callable.opt_args.size();
|
||||
|
||||
if (n_args < c_args_sz || (n_args - c_args_sz > c_opt_args_sz)) {
|
||||
if (callable.args.empty() && callable.opt_args.empty()) {
|
||||
throw QueryRuntimeException("'{}' requires no arguments.", fully_qualified_name);
|
||||
}
|
||||
|
||||
if (callable.opt_args.empty()) {
|
||||
throw QueryRuntimeException("'{}' requires exactly {} {}.", fully_qualified_name, c_args_sz,
|
||||
c_args_sz == 1U ? "argument" : "arguments");
|
||||
}
|
||||
|
||||
throw QueryRuntimeException("'{}' requires between {} and {} arguments.", fully_qualified_name, c_args_sz,
|
||||
c_args_sz + c_opt_args_sz);
|
||||
}
|
||||
args_list.elems.reserve(n_args);
|
||||
|
||||
auto is_not_optional_arg = [c_args_sz](int i) { return c_args_sz > i; };
|
||||
for (size_t i = 0; i < n_args; ++i) {
|
||||
auto arg = args[i];
|
||||
std::string_view name;
|
||||
const query::v2::procedure::CypherType *type;
|
||||
if (is_not_optional_arg(i)) {
|
||||
name = callable.args[i].first;
|
||||
type = callable.args[i].second;
|
||||
} else {
|
||||
name = std::get<0>(callable.opt_args[i - c_args_sz]);
|
||||
type = std::get<1>(callable.opt_args[i - c_args_sz]);
|
||||
}
|
||||
if (!type->SatisfiesType(arg)) {
|
||||
throw QueryRuntimeException("'{}' argument named '{}' at position {} must be of type {}.", fully_qualified_name,
|
||||
name, i, type->GetPresentableName());
|
||||
}
|
||||
args_list.elems.emplace_back(std::move(arg), &graph);
|
||||
}
|
||||
// Fill missing optional arguments with their default values.
|
||||
const size_t passed_in_opt_args = n_args - c_args_sz;
|
||||
for (size_t i = passed_in_opt_args; i < c_opt_args_sz; ++i) {
|
||||
args_list.elems.emplace_back(std::get<2>(callable.opt_args[i]), &graph);
|
||||
}
|
||||
}
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
2650
src/query/v2/procedure/py_module.cpp
Normal file
2650
src/query/v2/procedure/py_module.cpp
Normal file
File diff suppressed because it is too large
Load Diff
82
src/query/v2/procedure/py_module.hpp
Normal file
82
src/query/v2/procedure/py_module.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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.
|
||||
|
||||
/// @file
|
||||
/// Functions and types for loading Query Modules written in Python.
|
||||
#pragma once
|
||||
|
||||
#include "py/py.hpp"
|
||||
|
||||
struct mgp_graph;
|
||||
struct mgp_memory;
|
||||
struct mgp_module;
|
||||
struct mgp_value;
|
||||
|
||||
namespace memgraph::query::v2::procedure {
|
||||
|
||||
struct PyGraph;
|
||||
|
||||
/// Convert an `mgp_value` into a Python object, referencing the given `PyGraph`
|
||||
/// instance and using the same allocator as the graph.
|
||||
///
|
||||
/// Values of type `MGP_VALUE_TYPE_VERTEX`, `MGP_VALUE_TYPE_EDGE` and
|
||||
/// `MGP_VALUE_TYPE_PATH` are returned as `mgp.Vertex`, `mgp.Edge` and
|
||||
/// `mgp.Path` respectively, and *not* their internal `_mgp`
|
||||
/// representations. Other value types are converted to equivalent builtin
|
||||
/// Python objects.
|
||||
///
|
||||
/// Return a non-null `py::Object` instance on success. Otherwise, return a null
|
||||
/// `py::Object` instance and set the appropriate Python exception.
|
||||
py::Object MgpValueToPyObject(const mgp_value &value, PyGraph *py_graph);
|
||||
|
||||
py::Object MgpValueToPyObject(const mgp_value &value, PyObject *py_graph);
|
||||
|
||||
/// Convert a Python object into `mgp_value`, constructing it using the given
|
||||
/// `mgp_memory` allocator.
|
||||
///
|
||||
/// If the user-facing 'mgp' module can be imported, this function will handle
|
||||
/// conversion of 'mgp.Vertex', 'mgp.Edge' and 'mgp.Path' values.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::overflow_error if attempting to convert a Python integer which
|
||||
/// too large to fit into int64_t.
|
||||
/// @throw std::invalid_argument if the given Python object cannot be converted
|
||||
/// to an mgp_value (e.g. a dictionary whose keys aren't strings or an object
|
||||
/// of unsupported type).
|
||||
mgp_value *PyObjectToMgpValue(PyObject *, mgp_memory *);
|
||||
|
||||
/// Create the _mgp module for use in embedded Python.
|
||||
///
|
||||
/// The function is to be used before Py_Initialize via the following code.
|
||||
///
|
||||
/// PyImport_AppendInittab("_mgp", &query::v2::procedure::PyInitMgpModule);
|
||||
PyObject *PyInitMgpModule();
|
||||
|
||||
/// Create an instance of _mgp.Graph class.
|
||||
PyObject *MakePyGraph(mgp_graph *, mgp_memory *);
|
||||
|
||||
/// Import a module with given name in the context of mgp_module.
|
||||
///
|
||||
/// This function can only be called when '_mgp' module has been initialized in
|
||||
/// Python.
|
||||
///
|
||||
/// Return nullptr and set appropriate Python exception on failure.
|
||||
py::Object ImportPyModule(const char *, mgp_module *);
|
||||
|
||||
/// Reload already loaded Python module in the context of mgp_module.
|
||||
///
|
||||
/// This function can only be called when '_mgp' module has been initialized in
|
||||
/// Python.
|
||||
///
|
||||
/// Return nullptr and set appropriate Python exception on failure.
|
||||
py::Object ReloadPyModule(PyObject *, mgp_module *);
|
||||
|
||||
} // namespace memgraph::query::v2::procedure
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
|
||||
namespace memgraph::msgs {
|
||||
|
||||
@@ -48,33 +47,42 @@ inline bool operator==(const VertexId &lhs, const VertexId &rhs) {
|
||||
|
||||
using Gid = size_t;
|
||||
using PropertyId = memgraph::storage::v3::PropertyId;
|
||||
using EdgeTypeId = memgraph::storage::v3::EdgeTypeId;
|
||||
|
||||
struct EdgeType {
|
||||
EdgeTypeId id;
|
||||
uint64_t id;
|
||||
friend bool operator==(const EdgeType &lhs, const EdgeType &rhs) = default;
|
||||
};
|
||||
|
||||
struct EdgeId {
|
||||
Gid gid;
|
||||
|
||||
friend bool operator==(const EdgeId &lhs, const EdgeId &rhs) { return lhs.gid == rhs.gid; }
|
||||
friend bool operator<(const EdgeId &lhs, const EdgeId &rhs) { return lhs.gid < rhs.gid; }
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
VertexId src;
|
||||
VertexId dst;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
EdgeId id;
|
||||
EdgeType type;
|
||||
friend bool operator==(const Edge &lhs, const Edge &rhs) { return lhs.id == rhs.id; }
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
VertexId id;
|
||||
std::vector<Label> labels;
|
||||
friend bool operator==(const Vertex &lhs, const Vertex &rhs) { return lhs.id == rhs.id; }
|
||||
friend bool operator==(const Vertex &lhs, const Vertex &rhs) {
|
||||
return (lhs.id == rhs.id) && (lhs.labels == rhs.labels);
|
||||
}
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
VertexId src;
|
||||
VertexId dst;
|
||||
EdgeId id;
|
||||
EdgeType type;
|
||||
friend bool operator==(const Edge &lhs, const Edge &rhs) {
|
||||
return (lhs.src == rhs.src) && (lhs.dst == rhs.dst) && (lhs.type == rhs.type);
|
||||
}
|
||||
};
|
||||
|
||||
struct PathPart {
|
||||
Vertex dst;
|
||||
Gid edge;
|
||||
};
|
||||
|
||||
struct Path {
|
||||
Vertex src;
|
||||
std::vector<PathPart> parts;
|
||||
};
|
||||
|
||||
struct Null {};
|
||||
@@ -126,9 +134,13 @@ struct Value {
|
||||
case Type::Map:
|
||||
std::destroy_at(&map_v);
|
||||
return;
|
||||
|
||||
case Type::Vertex:
|
||||
std::destroy_at(&vertex_v);
|
||||
return;
|
||||
case Type::Path:
|
||||
std::destroy_at(&path_v);
|
||||
return;
|
||||
case Type::Edge:
|
||||
std::destroy_at(&edge_v);
|
||||
}
|
||||
@@ -162,6 +174,9 @@ struct Value {
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(other.edge_v);
|
||||
return;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +208,9 @@ struct Value {
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(std::move(other.edge_v));
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
}
|
||||
|
||||
other.DestroyValue();
|
||||
@@ -232,6 +250,9 @@ struct Value {
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(other.edge_v);
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(other.path_v);
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
@@ -270,6 +291,9 @@ struct Value {
|
||||
case Type::Edge:
|
||||
new (&edge_v) Edge(std::move(other.edge_v));
|
||||
break;
|
||||
case Type::Path:
|
||||
new (&path_v) Path(std::move(other.path_v));
|
||||
break;
|
||||
}
|
||||
|
||||
other.DestroyValue();
|
||||
@@ -277,7 +301,7 @@ struct Value {
|
||||
|
||||
return *this;
|
||||
}
|
||||
enum class Type : uint8_t { Null, Bool, Int64, Double, String, List, Map, Vertex, Edge };
|
||||
enum class Type : uint8_t { Null, Bool, Int64, Double, String, List, Map, Vertex, Edge, Path };
|
||||
Type type{Type::Null};
|
||||
union {
|
||||
Null null_v;
|
||||
@@ -289,6 +313,7 @@ struct Value {
|
||||
std::map<std::string, Value> map_v;
|
||||
Vertex vertex_v;
|
||||
Edge edge_v;
|
||||
Path path_v;
|
||||
};
|
||||
|
||||
friend bool operator==(const Value &lhs, const Value &rhs) {
|
||||
@@ -314,10 +339,26 @@ struct Value {
|
||||
return lhs.vertex_v == rhs.vertex_v;
|
||||
case Value::Type::Edge:
|
||||
return lhs.edge_v == rhs.edge_v;
|
||||
case Value::Type::Path:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ValuesMap {
|
||||
std::unordered_map<PropertyId, Value> values_map;
|
||||
};
|
||||
|
||||
struct MappedValues {
|
||||
std::vector<ValuesMap> values_map;
|
||||
};
|
||||
|
||||
struct ListedValues {
|
||||
std::vector<std::vector<Value>> properties;
|
||||
};
|
||||
|
||||
using Values = std::variant<ListedValues, MappedValues>;
|
||||
|
||||
struct Expression {
|
||||
std::string expression;
|
||||
};
|
||||
@@ -337,29 +378,17 @@ enum class StorageView { OLD = 0, NEW = 1 };
|
||||
|
||||
struct ScanVerticesRequest {
|
||||
Hlc transaction_id;
|
||||
// This should be optional
|
||||
VertexId start_id;
|
||||
std::vector<VertexId> scanned_vertices;
|
||||
// The empty optional means return all of the properties, while an empty list means do not return any properties
|
||||
std::optional<std::vector<PropertyId>> props_to_return;
|
||||
// expression that determines if vertex is returned or not
|
||||
std::vector<std::string> filter_expressions;
|
||||
// expression whose result is returned for every vertex
|
||||
std::vector<std::string> vertex_expressions;
|
||||
std::optional<std::vector<std::string>> filter_expressions;
|
||||
std::optional<size_t> batch_limit;
|
||||
std::vector<OrderBy> order_bys;
|
||||
StorageView storage_view{StorageView::NEW};
|
||||
|
||||
std::optional<Label> label;
|
||||
std::optional<std::pair<PropertyId, std::string>> property_expression_pair;
|
||||
StorageView storage_view;
|
||||
};
|
||||
|
||||
struct ScanResultRow {
|
||||
Vertex vertex;
|
||||
// empty() is no properties returned
|
||||
// This should be changed to std::map<PropertyId, Value>
|
||||
std::vector<std::pair<PropertyId, Value>> props;
|
||||
std::vector<Value> evaluated_vertex_expressions;
|
||||
};
|
||||
|
||||
struct ScanVerticesResponse {
|
||||
@@ -372,7 +401,6 @@ using VertexOrEdgeIds = std::variant<VertexId, EdgeId>;
|
||||
|
||||
struct GetPropertiesRequest {
|
||||
Hlc transaction_id;
|
||||
// Shouldn't contain mixed vertex and edge ids
|
||||
VertexOrEdgeIds vertex_or_edge_ids;
|
||||
std::vector<PropertyId> property_ids;
|
||||
std::vector<Expression> expressions;
|
||||
@@ -384,48 +412,44 @@ struct GetPropertiesRequest {
|
||||
|
||||
struct GetPropertiesResponse {
|
||||
bool success;
|
||||
Values values;
|
||||
};
|
||||
|
||||
enum class EdgeDirection : uint8_t { OUT = 1, IN = 2, BOTH = 3 };
|
||||
|
||||
struct VertexEdgeId {
|
||||
VertexId vertex_id;
|
||||
std::optional<EdgeId> next_id;
|
||||
};
|
||||
|
||||
struct ExpandOneRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<VertexId> src_vertices;
|
||||
// return types that type is in this list
|
||||
// empty means all the types
|
||||
std::vector<EdgeType> edge_types;
|
||||
EdgeDirection direction{EdgeDirection::OUT};
|
||||
// Wether to return multiple edges between the same neighbors
|
||||
EdgeDirection direction;
|
||||
bool only_unique_neighbor_rows = false;
|
||||
// The empty optional means return all of the properties, while an empty list means do not return any properties
|
||||
// The empty optional means return all of the properties, while an empty
|
||||
// list means do not return any properties
|
||||
// TODO(antaljanosbenjamin): All of the special values should be communicated through a single vertex object
|
||||
// after schema is implemented
|
||||
// Special values are accepted:
|
||||
// * __mg__labels
|
||||
std::optional<std::vector<PropertyId>> src_vertex_properties;
|
||||
// The empty optional means return all of the properties, while an empty list means do not return any properties
|
||||
// TODO(antaljanosbenjamin): All of the special values should be communicated through a single vertex object
|
||||
// after schema is implemented
|
||||
// Special values are accepted:
|
||||
// * __mg__dst_id (Vertex, but without labels)
|
||||
// * __mg__type (binary)
|
||||
std::optional<std::vector<PropertyId>> edge_properties;
|
||||
|
||||
std::vector<std::string> vertex_expressions;
|
||||
std::vector<std::string> edge_expressions;
|
||||
|
||||
// QUESTION(antaljanosbenjamin): Maybe also add possibility to expressions evaluated on the source vertex?
|
||||
// List of expressions evaluated on edges
|
||||
std::vector<Expression> expressions;
|
||||
std::optional<std::vector<OrderBy>> order_by;
|
||||
// Limit the edges or the vertices?
|
||||
std::optional<size_t> limit;
|
||||
std::vector<std::string> filters;
|
||||
std::optional<Filter> filter;
|
||||
};
|
||||
|
||||
struct ExpandOneResultRow {
|
||||
struct EdgeWithAllProperties {
|
||||
VertexId other_end;
|
||||
EdgeType type;
|
||||
Gid gid;
|
||||
std::map<PropertyId, Value> properties;
|
||||
};
|
||||
|
||||
struct EdgeWithSpecificProperties {
|
||||
VertexId other_end;
|
||||
EdgeType type;
|
||||
Gid gid;
|
||||
std::vector<Value> properties;
|
||||
};
|
||||
|
||||
// NOTE: This struct could be a single Values with columns something like this:
|
||||
// src_vertex(Vertex), vertex_prop1(Value), vertex_prop2(Value), edges(list<Value>)
|
||||
// where edges might be a list of:
|
||||
@@ -434,35 +458,21 @@ struct ExpandOneResultRow {
|
||||
// The drawback of this is currently the key of the map is always interpreted as a string in Value, not as an
|
||||
// integer, which should be in case of mapped properties.
|
||||
Vertex src_vertex;
|
||||
std::map<PropertyId, Value> src_vertex_properties;
|
||||
|
||||
// NOTE: If the desired edges are specified in the request,
|
||||
// edges_with_specific_properties will have a value and it will
|
||||
// return the properties as a vector of property values. The order
|
||||
// of the values returned should be the same as the PropertyIds
|
||||
// were defined in the request.
|
||||
std::vector<EdgeWithAllProperties> in_edges_with_all_properties;
|
||||
std::vector<EdgeWithSpecificProperties> in_edges_with_specific_properties;
|
||||
std::vector<EdgeWithAllProperties> out_edges_with_all_properties;
|
||||
std::vector<EdgeWithSpecificProperties> out_edges_with_specific_properties;
|
||||
std::optional<Values> src_vertex_properties;
|
||||
Values edges;
|
||||
};
|
||||
|
||||
struct ExpandOneResponse {
|
||||
bool success;
|
||||
std::vector<ExpandOneResultRow> result;
|
||||
};
|
||||
|
||||
struct UpdateVertexProp {
|
||||
PrimaryKey primary_key;
|
||||
// This should be a map
|
||||
VertexId primary_key;
|
||||
std::vector<std::pair<PropertyId, Value>> property_updates;
|
||||
};
|
||||
|
||||
struct UpdateEdgeProp {
|
||||
EdgeId edge_id;
|
||||
VertexId src;
|
||||
VertexId dst;
|
||||
// This should be a map
|
||||
Edge edge;
|
||||
std::vector<std::pair<PropertyId, Value>> property_updates;
|
||||
};
|
||||
|
||||
@@ -472,11 +482,17 @@ struct UpdateEdgeProp {
|
||||
struct NewVertex {
|
||||
std::vector<Label> label_ids;
|
||||
PrimaryKey primary_key;
|
||||
// This should be a map
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
struct NewVertexLabel {
|
||||
std::string label;
|
||||
PrimaryKey primary_key;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
struct CreateVerticesRequest {
|
||||
std::string label;
|
||||
Hlc transaction_id;
|
||||
std::vector<NewVertex> new_vertices;
|
||||
};
|
||||
@@ -508,22 +524,12 @@ struct UpdateVerticesResponse {
|
||||
/*
|
||||
* Edges
|
||||
*/
|
||||
// No need for specifying direction since it has to be in one, and src and dest
|
||||
// vertices clearly communicate the direction
|
||||
struct NewExpand {
|
||||
EdgeId id;
|
||||
EdgeType type;
|
||||
VertexId src_vertex;
|
||||
VertexId dest_vertex;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
struct CreateExpandRequest {
|
||||
struct CreateEdgesRequest {
|
||||
Hlc transaction_id;
|
||||
std::vector<NewExpand> new_expands;
|
||||
std::vector<Edge> edges;
|
||||
};
|
||||
|
||||
struct CreateExpandResponse {
|
||||
struct CreateEdgesResponse {
|
||||
bool success;
|
||||
};
|
||||
|
||||
@@ -558,53 +564,8 @@ using ReadRequests = std::variant<ExpandOneRequest, GetPropertiesRequest, ScanVe
|
||||
using ReadResponses = std::variant<ExpandOneResponse, GetPropertiesResponse, ScanVerticesResponse>;
|
||||
|
||||
using WriteRequests = std::variant<CreateVerticesRequest, DeleteVerticesRequest, UpdateVerticesRequest,
|
||||
CreateExpandRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
|
||||
CreateEdgesRequest, DeleteEdgesRequest, UpdateEdgesRequest, CommitRequest>;
|
||||
using WriteResponses = std::variant<CreateVerticesResponse, DeleteVerticesResponse, UpdateVerticesResponse,
|
||||
CreateExpandResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;
|
||||
CreateEdgesResponse, DeleteEdgesResponse, UpdateEdgesResponse, CommitResponse>;
|
||||
|
||||
} // namespace memgraph::msgs
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::msgs::Value>;
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::msgs::VertexId> {
|
||||
size_t operator()(const memgraph::msgs::VertexId &id) const {
|
||||
using LabelId = memgraph::storage::v3::LabelId;
|
||||
using Value = memgraph::msgs::Value;
|
||||
return memgraph::utils::HashCombine<LabelId, std::vector<Value>, std::hash<LabelId>,
|
||||
memgraph::utils::FnvCollection<std::vector<Value>, Value>>{}(id.first.id,
|
||||
id.second);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::msgs::Value> {
|
||||
size_t operator()(const memgraph::msgs::Value &value) const {
|
||||
using Type = memgraph::msgs::Value::Type;
|
||||
switch (value.type) {
|
||||
case Type::Null:
|
||||
return std::hash<size_t>{}(0U);
|
||||
case Type::Bool:
|
||||
return std::hash<bool>{}(value.bool_v);
|
||||
case Type::Int64:
|
||||
return std::hash<int64_t>{}(value.int_v);
|
||||
case Type::Double:
|
||||
return std::hash<double>{}(value.double_v);
|
||||
case Type::String:
|
||||
return std::hash<std::string>{}(value.string_v);
|
||||
case Type::List:
|
||||
LOG_FATAL("Add hash for lists");
|
||||
case Type::Map:
|
||||
LOG_FATAL("Add hash for maps");
|
||||
case Type::Vertex:
|
||||
LOG_FATAL("Add hash for vertices");
|
||||
case Type::Edge:
|
||||
LOG_FATAL("Add hash for edges");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
@@ -25,7 +23,6 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "coordinator/coordinator.hpp"
|
||||
#include "coordinator/coordinator_client.hpp"
|
||||
#include "coordinator/coordinator_rsm.hpp"
|
||||
#include "coordinator/shard_map.hpp"
|
||||
@@ -56,20 +53,18 @@ class RsmStorageClientManager {
|
||||
RsmStorageClientManager &operator=(RsmStorageClientManager &&) = delete;
|
||||
~RsmStorageClientManager() = default;
|
||||
|
||||
void AddClient(Shard key, TStorageClient client) { cli_cache_.emplace(std::move(key), std::move(client)); }
|
||||
void AddClient(const LabelId label_id, Shard key, TStorageClient client) {
|
||||
cli_cache_[label_id].insert({std::move(key), std::move(client)});
|
||||
}
|
||||
|
||||
bool Exists(const Shard &key) { return cli_cache_.contains(key); }
|
||||
bool Exists(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].contains(key); }
|
||||
|
||||
void PurgeCache() { cli_cache_.clear(); }
|
||||
|
||||
TStorageClient &GetClient(const Shard &key) {
|
||||
auto it = cli_cache_.find(key);
|
||||
MG_ASSERT(it != cli_cache_.end(), "Non-existing shard client");
|
||||
return it->second;
|
||||
}
|
||||
TStorageClient &GetClient(const LabelId label_id, const Shard &key) { return cli_cache_[label_id].find(key)->second; }
|
||||
|
||||
private:
|
||||
std::map<Shard, TStorageClient> cli_cache_;
|
||||
std::map<LabelId, std::map<Shard, TStorageClient>> cli_cache_;
|
||||
};
|
||||
|
||||
template <typename TRequest>
|
||||
@@ -90,14 +85,9 @@ struct ExecutionState {
|
||||
// the ShardRequestManager impl will send requests to. When a request to a shard exhausts it, meaning that
|
||||
// it pulled all the requested data from the given Shard, it will be removed from the Vector. When the Vector becomes
|
||||
// empty, it means that all of the requests have completed succefully.
|
||||
// TODO(gvolfing)
|
||||
// Maybe make this into a more complex object to be able to keep track of paginated resutls. E.g. instead of a vector
|
||||
// of Shards make it into a std::vector<std::pair<Shard, PaginatedResultType>> (probably a struct instead of a pair)
|
||||
// where PaginatedResultType is an enum signaling the progress on the given request. This way we can easily check if
|
||||
// a partial response on a shard(if there is one) is finished and we can send off the request for the next batch.
|
||||
std::vector<Shard> shard_cache;
|
||||
// 1-1 mapping with `shard_cache`.
|
||||
// A vector that tracks request metadata for each shard (For example, next_id for a ScanAll on Shard A)
|
||||
// A vector that tracks request metatdata for each shard (For example, next_id for a ScanAll on Shard A)
|
||||
std::vector<TRequest> requests;
|
||||
State state = INITIALIZING;
|
||||
};
|
||||
@@ -114,34 +104,25 @@ class ShardRequestManagerInterface {
|
||||
virtual ~ShardRequestManagerInterface() = default;
|
||||
|
||||
virtual void StartTransaction() = 0;
|
||||
virtual void Commit() = 0;
|
||||
virtual std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state,
|
||||
std::vector<VertexId> &&scanned_vertices) = 0;
|
||||
virtual std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) = 0;
|
||||
virtual std::vector<CreateVerticesResponse> Request(ExecutionState<CreateVerticesRequest> &state,
|
||||
std::vector<NewVertex> new_vertices) = 0;
|
||||
virtual std::vector<ExpandOneResultRow> Request(ExecutionState<ExpandOneRequest> &state,
|
||||
ExpandOneRequest request) = 0;
|
||||
virtual std::vector<CreateExpandResponse> Request(ExecutionState<CreateExpandRequest> &state,
|
||||
std::vector<NewExpand> new_edges) = 0;
|
||||
|
||||
virtual storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const = 0;
|
||||
virtual storage::v3::PropertyId NameToProperty(const std::string &name) const = 0;
|
||||
virtual storage::v3::LabelId NameToLabel(const std::string &name) const = 0;
|
||||
virtual const std::string &PropertyToName(memgraph::storage::v3::PropertyId prop) const = 0;
|
||||
virtual const std::string &LabelToName(memgraph::storage::v3::LabelId label) const = 0;
|
||||
virtual const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId type) const = 0;
|
||||
virtual bool IsPrimaryLabel(LabelId label) const = 0;
|
||||
virtual bool IsPrimaryProperty(LabelId primary_label, PropertyId property) const = 0;
|
||||
virtual bool IsPrimaryKey(LabelId primary_label, PropertyId property) const = 0;
|
||||
virtual std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) = 0;
|
||||
virtual memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const = 0;
|
||||
virtual memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const = 0;
|
||||
virtual bool IsPrimaryKey(PropertyId name) const = 0;
|
||||
};
|
||||
|
||||
// TODO(kostasrim)rename this class template
|
||||
template <typename TTransport>
|
||||
class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
public:
|
||||
using WriteRequests = CreateVerticesRequest;
|
||||
using WriteResponses = CreateVerticesResponse;
|
||||
using ReadRequests = std::variant<ScanVerticesRequest, ExpandOneRequest>;
|
||||
using ReadResponses = std::variant<ScanVerticesResponse, ExpandOneResponse>;
|
||||
using StorageClient =
|
||||
memgraph::coordinator::RsmClient<TTransport, WriteRequests, WriteResponses, ReadRequests, ReadResponses>;
|
||||
using CoordinatorWriteRequests = memgraph::coordinator::CoordinatorWriteRequests;
|
||||
using CoordinatorClient = memgraph::coordinator::CoordinatorClient<TTransport>;
|
||||
using Address = memgraph::io::Address;
|
||||
using Shard = memgraph::coordinator::Shard;
|
||||
@@ -160,8 +141,7 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
|
||||
void StartTransaction() override {
|
||||
memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()};
|
||||
CoordinatorWriteRequests write_req = req;
|
||||
auto write_res = coord_cli_.SendWriteRequest(write_req);
|
||||
auto write_res = coord_cli_.SendWriteRequest(req);
|
||||
if (write_res.HasError()) {
|
||||
throw std::runtime_error("HLC request failed");
|
||||
}
|
||||
@@ -173,110 +153,52 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
|
||||
if (hlc_response.fresher_shard_map) {
|
||||
shards_map_ = hlc_response.fresher_shard_map.value();
|
||||
SetUpNameIdMappers();
|
||||
}
|
||||
}
|
||||
|
||||
void Commit() override {
|
||||
memgraph::coordinator::HlcRequest req{.last_shard_map_version = shards_map_.GetHlc()};
|
||||
CoordinatorWriteRequests write_req = req;
|
||||
auto write_res = coord_cli_.SendWriteRequest(write_req);
|
||||
if (write_res.HasError()) {
|
||||
throw std::runtime_error("HLC request for commit failed");
|
||||
}
|
||||
auto coordinator_write_response = write_res.GetValue();
|
||||
auto hlc_response = std::get<memgraph::coordinator::HlcResponse>(coordinator_write_response);
|
||||
|
||||
if (hlc_response.fresher_shard_map) {
|
||||
shards_map_ = hlc_response.fresher_shard_map.value();
|
||||
SetUpNameIdMappers();
|
||||
}
|
||||
auto commit_timestamp = hlc_response.new_hlc;
|
||||
|
||||
msgs::CommitRequest commit_req{.transaction_id = transaction_id_, .commit_timestamp = commit_timestamp};
|
||||
|
||||
for (const auto &[label, space] : shards_map_.label_spaces) {
|
||||
for (const auto &[key, shard] : space.shards) {
|
||||
auto &storage_client = GetStorageClientForShard(shard);
|
||||
// TODO(kostasrim) Currently requests return the result directly. Adjust this when the API works MgFuture
|
||||
// instead.
|
||||
auto commit_response = storage_client.SendWriteRequest(commit_req);
|
||||
// RETRY on timeouts?
|
||||
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test
|
||||
if (commit_response.HasError()) {
|
||||
throw std::runtime_error("Commit request timed out");
|
||||
}
|
||||
WriteResponses write_response_variant = commit_response.GetValue();
|
||||
auto &response = std::get<CommitResponse>(write_response_variant);
|
||||
if (!response.success) {
|
||||
throw std::runtime_error("Commit request did not succeed");
|
||||
}
|
||||
}
|
||||
}
|
||||
memgraph::storage::v3::PropertyId NameToProperty(const std::string &name) const override {
|
||||
return *shards_map_.GetPropertyId(name);
|
||||
}
|
||||
|
||||
storage::v3::EdgeTypeId NameToEdgeType(const std::string &name) const override {
|
||||
return shards_map_.GetEdgeTypeId(name).value();
|
||||
memgraph::storage::v3::LabelId LabelNameToLabelId(const std::string &name) const override {
|
||||
return shards_map_.GetLabelId(name);
|
||||
}
|
||||
|
||||
storage::v3::PropertyId NameToProperty(const std::string &name) const override {
|
||||
return shards_map_.GetPropertyId(name).value();
|
||||
}
|
||||
|
||||
storage::v3::LabelId NameToLabel(const std::string &name) const override {
|
||||
return shards_map_.GetLabelId(name).value();
|
||||
}
|
||||
|
||||
const std::string &PropertyToName(memgraph::storage::v3::PropertyId id) const override {
|
||||
return properties_.IdToName(id.AsUint());
|
||||
}
|
||||
const std::string &LabelToName(memgraph::storage::v3::LabelId id) const override {
|
||||
return labels_.IdToName(id.AsUint());
|
||||
}
|
||||
const std::string &EdgeTypeToName(memgraph::storage::v3::EdgeTypeId id) const override {
|
||||
return edge_types_.IdToName(id.AsUint());
|
||||
}
|
||||
|
||||
bool IsPrimaryLabel(LabelId label) const override { return shards_map_.label_spaces.contains(label); }
|
||||
|
||||
bool IsPrimaryProperty(LabelId primary_label, PropertyId property) const override {
|
||||
const auto schema_it = shards_map_.schemas.find(primary_label);
|
||||
MG_ASSERT(schema_it != shards_map_.schemas.end(), "Invalid primary label id: {}", primary_label.AsUint());
|
||||
|
||||
return std::find_if(schema_it->second.begin(), schema_it->second.end(), [property](const auto &schema_prop) {
|
||||
return schema_prop.property_id == property;
|
||||
}) != schema_it->second.end();
|
||||
}
|
||||
|
||||
bool IsPrimaryKey(LabelId primary_label, PropertyId property) const override {
|
||||
const auto schema_it = shards_map_.schemas.find(primary_label);
|
||||
MG_ASSERT(schema_it != shards_map_.schemas.end(), "Invalid primary label id: {}", primary_label.AsUint());
|
||||
|
||||
return schema_it->second.size() == 1 && schema_it->second[0].property_id == property;
|
||||
bool IsPrimaryKey(const PropertyId name) const override {
|
||||
return std::find_if(shards_map_.properties.begin(), shards_map_.properties.end(),
|
||||
[name](auto &pr) { return pr.second == name; }) != shards_map_.properties.end();
|
||||
}
|
||||
|
||||
// TODO(kostasrim) Simplify return result
|
||||
std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state,
|
||||
std::vector<VertexId> &&scanned_vertices) override {
|
||||
MaybeInitializeExecutionState(state, std::move(scanned_vertices));
|
||||
std::vector<VertexAccessor> Request(ExecutionState<ScanVerticesRequest> &state) override {
|
||||
MaybeInitializeExecutionState(state);
|
||||
std::vector<ScanVerticesResponse> responses;
|
||||
|
||||
SendAllRequests(state);
|
||||
auto all_requests_gathered = [](auto &paginated_rsp_tracker) {
|
||||
return std::ranges::all_of(paginated_rsp_tracker, [](const auto &state) {
|
||||
return state.second == PaginatedResponseState::PartiallyFinished;
|
||||
});
|
||||
};
|
||||
|
||||
std::map<Shard, PaginatedResponseState> paginated_response_tracker;
|
||||
for (const auto &shard : state.shard_cache) {
|
||||
paginated_response_tracker.insert(std::make_pair(shard, PaginatedResponseState::Pending));
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
size_t id = 0;
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
|
||||
auto &storage_client = GetStorageClientForShard(
|
||||
*state.label, storage::conversions::ConvertPropertyVector(state.requests[id].start_id.second));
|
||||
// TODO(kostasrim) Currently requests return the result directly. Adjust this when the API works MgFuture
|
||||
// instead.
|
||||
auto read_response_result = storage_client.SendReadRequest(state.requests[id]);
|
||||
// RETRY on timeouts?
|
||||
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test
|
||||
if (read_response_result.HasError()) {
|
||||
throw std::runtime_error("ScanAll request timedout");
|
||||
}
|
||||
auto &response = std::get<ScanVerticesResponse>(read_response_result.GetValue());
|
||||
if (!response.success) {
|
||||
throw std::runtime_error("ScanAll request did not succeed");
|
||||
}
|
||||
if (!response.next_start_id) {
|
||||
shard_it = shard_cache_ref.erase(shard_it);
|
||||
} else {
|
||||
state.requests[id].start_id.second = response.next_start_id->second;
|
||||
++shard_it;
|
||||
}
|
||||
responses.push_back(std::move(response));
|
||||
}
|
||||
|
||||
do {
|
||||
AwaitOnPaginatedRequests(state, responses, paginated_response_tracker);
|
||||
} while (!all_requests_gathered(paginated_response_tracker));
|
||||
|
||||
// We are done with this state
|
||||
MaybeCompleteState(state);
|
||||
// TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return
|
||||
// result of storage_client.SendReadRequest()).
|
||||
@@ -289,88 +211,63 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
MaybeInitializeExecutionState(state, new_vertices);
|
||||
std::vector<CreateVerticesResponse> responses;
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
|
||||
// 1. Send the requests.
|
||||
SendAllRequests(state, shard_cache_ref);
|
||||
|
||||
// 2. Block untill all the futures are exhausted
|
||||
do {
|
||||
AwaitOnResponses(state, responses);
|
||||
} while (!state.shard_cache.empty());
|
||||
|
||||
size_t id = 0;
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
|
||||
// This is fine because all new_vertices of each request end up on the same shard
|
||||
const auto labels = state.requests[id].new_vertices[0].label_ids;
|
||||
auto primary_key = state.requests[id].new_vertices[0].primary_key;
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it, labels[0].id);
|
||||
auto write_response_result = storage_client.SendWriteRequest(state.requests[id]);
|
||||
// RETRY on timeouts?
|
||||
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map test
|
||||
if (write_response_result.HasError()) {
|
||||
throw std::runtime_error("CreateVertices request timedout");
|
||||
}
|
||||
if (!write_response_result.GetValue().success) {
|
||||
throw std::runtime_error("CreateVertices request did not succeed");
|
||||
}
|
||||
responses.push_back(write_response_result.GetValue());
|
||||
shard_it = shard_cache_ref.erase(shard_it);
|
||||
}
|
||||
// We are done with this state
|
||||
MaybeCompleteState(state);
|
||||
// TODO(kostasrim) Before returning start prefetching the batch (this shall be done once we get MgFuture as return
|
||||
// result of storage_client.SendReadRequest()).
|
||||
return responses;
|
||||
}
|
||||
|
||||
std::vector<CreateExpandResponse> Request(ExecutionState<CreateExpandRequest> &state,
|
||||
std::vector<NewExpand> new_edges) override {
|
||||
MG_ASSERT(!new_edges.empty());
|
||||
MaybeInitializeExecutionState(state, new_edges);
|
||||
std::vector<CreateExpandResponse> responses;
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
size_t id{0};
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
WriteRequests req = state.requests[id];
|
||||
auto write_response_result = storage_client.SendWriteRequest(std::move(req));
|
||||
if (write_response_result.HasError()) {
|
||||
throw std::runtime_error("CreateVertices request timedout");
|
||||
}
|
||||
WriteResponses response_variant = write_response_result.GetValue();
|
||||
CreateExpandResponse mapped_response = std::get<CreateExpandResponse>(response_variant);
|
||||
|
||||
if (!mapped_response.success) {
|
||||
throw std::runtime_error("CreateExpand request did not succeed");
|
||||
}
|
||||
responses.push_back(mapped_response);
|
||||
shard_it = shard_cache_ref.erase(shard_it);
|
||||
}
|
||||
// We are done with this state
|
||||
MaybeCompleteState(state);
|
||||
return responses;
|
||||
}
|
||||
|
||||
std::vector<ExpandOneResultRow> Request(ExecutionState<ExpandOneRequest> &state, ExpandOneRequest request) override {
|
||||
std::vector<ExpandOneResponse> Request(ExecutionState<ExpandOneRequest> &state) override {
|
||||
// TODO(kostasrim)Update to limit the batch size here
|
||||
// Expansions of the destination must be handled by the caller. For example
|
||||
// match (u:L1 { prop : 1 })-[:Friend]-(v:L1)
|
||||
// For each vertex U, the ExpandOne will result in <U, Edges>. The destination vertex and its properties
|
||||
// must be fetched again with an ExpandOne(Edges.dst)
|
||||
MaybeInitializeExecutionState(state, std::move(request));
|
||||
MaybeInitializeExecutionState(state);
|
||||
std::vector<ExpandOneResponse> responses;
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
|
||||
// 1. Send the requests.
|
||||
SendAllRequests(state, shard_cache_ref);
|
||||
|
||||
// 2. Block untill all the futures are exhausted
|
||||
do {
|
||||
AwaitOnResponses(state, responses);
|
||||
} while (!state.shard_cache.empty());
|
||||
std::vector<ExpandOneResultRow> result_rows;
|
||||
const auto total_row_count = std::accumulate(
|
||||
responses.begin(), responses.end(), 0,
|
||||
[](const int64_t partial_count, const ExpandOneResponse &resp) { return partial_count + resp.result.size(); });
|
||||
result_rows.reserve(total_row_count);
|
||||
|
||||
for (auto &response : responses) {
|
||||
result_rows.insert(result_rows.end(), std::make_move_iterator(response.result.begin()),
|
||||
std::make_move_iterator(response.result.end()));
|
||||
size_t id = 0;
|
||||
// pending_requests on shards
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++id) {
|
||||
const Label primary_label = state.requests[id].src_vertices[0].first;
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it, primary_label.id);
|
||||
auto read_response_result = storage_client.SendReadRequest(state.requests[id]);
|
||||
// RETRY on timeouts?
|
||||
// Sometimes this produces a timeout. Temporary solution is to use a while(true) as was done in shard_map
|
||||
if (read_response_result.HasError()) {
|
||||
throw std::runtime_error("ExpandOne request timedout");
|
||||
}
|
||||
auto &response = std::get<ExpandOneResponse>(read_response_result.GetValue());
|
||||
responses.push_back(std::move(response));
|
||||
}
|
||||
MaybeCompleteState(state);
|
||||
return result_rows;
|
||||
return responses;
|
||||
}
|
||||
|
||||
private:
|
||||
enum class PaginatedResponseState { Pending, PartiallyFinished };
|
||||
|
||||
std::vector<VertexAccessor> PostProcess(std::vector<ScanVerticesResponse> &&responses) const {
|
||||
std::vector<VertexAccessor> accessors;
|
||||
for (auto &response : responses) {
|
||||
for (auto &result_row : response.results) {
|
||||
accessors.emplace_back(VertexAccessor(std::move(result_row.vertex), std::move(result_row.props), this));
|
||||
accessors.emplace_back(VertexAccessor(std::move(result_row.vertex), std::move(result_row.props)));
|
||||
}
|
||||
}
|
||||
return accessors;
|
||||
@@ -406,9 +303,8 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
std::map<Shard, CreateVerticesRequest> per_shard_request_table;
|
||||
|
||||
for (auto &new_vertex : new_vertices) {
|
||||
MG_ASSERT(!new_vertex.label_ids.empty(), "This is error!");
|
||||
const auto &shard = shards_map_.GetShardForKey(
|
||||
new_vertex.label_ids[0].id, storage::conversions::ConvertPropertyVector(new_vertex.primary_key));
|
||||
auto shard = shards_map_.GetShardForKey(new_vertex.label_ids[0].id,
|
||||
storage::conversions::ConvertPropertyVector(new_vertex.primary_key));
|
||||
if (!per_shard_request_table.contains(shard)) {
|
||||
CreateVerticesRequest create_v_rqst{.transaction_id = transaction_id_};
|
||||
per_shard_request_table.insert(std::pair(shard, std::move(create_v_rqst)));
|
||||
@@ -423,91 +319,24 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
state.state = ExecutionState<CreateVerticesRequest>::EXECUTING;
|
||||
}
|
||||
|
||||
void MaybeInitializeExecutionState(ExecutionState<CreateExpandRequest> &state, std::vector<NewExpand> new_expands) {
|
||||
void MaybeInitializeExecutionState(ExecutionState<ScanVerticesRequest> &state) {
|
||||
ThrowIfStateCompleted(state);
|
||||
if (ShallNotInitializeState(state)) {
|
||||
return;
|
||||
}
|
||||
state.transaction_id = transaction_id_;
|
||||
|
||||
std::map<Shard, CreateExpandRequest> per_shard_request_table;
|
||||
auto ensure_shard_exists_in_table = [&per_shard_request_table,
|
||||
transaction_id = transaction_id_](const Shard &shard) {
|
||||
if (!per_shard_request_table.contains(shard)) {
|
||||
CreateExpandRequest create_expand_request{.transaction_id = transaction_id};
|
||||
per_shard_request_table.insert({shard, std::move(create_expand_request)});
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &new_expand : new_expands) {
|
||||
const auto &shard_src_vertex = shards_map_.GetShardForKey(
|
||||
new_expand.src_vertex.first.id, storage::conversions::ConvertPropertyVector(new_expand.src_vertex.second));
|
||||
const auto &shard_dest_vertex = shards_map_.GetShardForKey(
|
||||
new_expand.dest_vertex.first.id, storage::conversions::ConvertPropertyVector(new_expand.dest_vertex.second));
|
||||
|
||||
ensure_shard_exists_in_table(shard_src_vertex);
|
||||
|
||||
if (shard_src_vertex != shard_dest_vertex) {
|
||||
ensure_shard_exists_in_table(shard_dest_vertex);
|
||||
per_shard_request_table[shard_dest_vertex].new_expands.push_back(new_expand);
|
||||
}
|
||||
per_shard_request_table[shard_src_vertex].new_expands.push_back(std::move(new_expand));
|
||||
}
|
||||
|
||||
for (auto &[shard, request] : per_shard_request_table) {
|
||||
state.shard_cache.push_back(shard);
|
||||
state.requests.push_back(std::move(request));
|
||||
}
|
||||
state.state = ExecutionState<CreateExpandRequest>::EXECUTING;
|
||||
}
|
||||
|
||||
void MaybeInitializeExecutionState(ExecutionState<ScanVerticesRequest> &state,
|
||||
std::vector<VertexId> &&scanned_vertices) {
|
||||
ThrowIfStateCompleted(state);
|
||||
if (ShallNotInitializeState(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scanned_vertices.empty()) {
|
||||
MG_ASSERT(state.label.has_value(), "Fill out the label member of the state!");
|
||||
std::map<Shard, ScanVerticesRequest> shards_and_requests;
|
||||
for (auto &vertex_id : scanned_vertices) {
|
||||
const auto &shard = shards_map_.GetShardForKey(vertex_id.first.id,
|
||||
storage::conversions::ConvertPropertyVector(vertex_id.second));
|
||||
shards_and_requests[shard].scanned_vertices.push_back(std::move(vertex_id));
|
||||
}
|
||||
|
||||
for (auto &[shard, request] : shards_and_requests) {
|
||||
state.shard_cache.push_back(shard);
|
||||
request.transaction_id = transaction_id_;
|
||||
state.requests.push_back(std::move(request));
|
||||
}
|
||||
} else {
|
||||
std::vector<coordinator::Shards> multi_shards;
|
||||
state.transaction_id = transaction_id_;
|
||||
if (!state.label) {
|
||||
multi_shards = shards_map_.GetAllShards();
|
||||
} else {
|
||||
const auto label_id = shards_map_.GetLabelId(*state.label);
|
||||
MG_ASSERT(label_id);
|
||||
MG_ASSERT(IsPrimaryLabel(*label_id));
|
||||
multi_shards = {shards_map_.GetShardsForLabel(*state.label)};
|
||||
}
|
||||
for (auto &shards : multi_shards) {
|
||||
for (auto &[key, shard] : shards) {
|
||||
MG_ASSERT(!shard.empty());
|
||||
state.shard_cache.push_back(std::move(shard));
|
||||
ScanVerticesRequest rqst;
|
||||
rqst.transaction_id = transaction_id_;
|
||||
rqst.start_id.second = storage::conversions::ConvertValueVector(key);
|
||||
state.requests.push_back(std::move(rqst));
|
||||
}
|
||||
}
|
||||
auto shards = shards_map_.GetShards(*state.label);
|
||||
for (auto &[key, shard] : shards) {
|
||||
state.shard_cache.push_back(std::move(shard));
|
||||
ScanVerticesRequest rqst;
|
||||
rqst.transaction_id = transaction_id_;
|
||||
rqst.start_id.second = storage::conversions::ConvertValueVector(key);
|
||||
state.requests.push_back(std::move(rqst));
|
||||
}
|
||||
state.state = ExecutionState<ScanVerticesRequest>::EXECUTING;
|
||||
}
|
||||
|
||||
void MaybeInitializeExecutionState(ExecutionState<ExpandOneRequest> &state, ExpandOneRequest request) {
|
||||
void MaybeInitializeExecutionState(ExecutionState<ExpandOneRequest> &state) {
|
||||
ThrowIfStateCompleted(state);
|
||||
if (ShallNotInitializeState(state)) {
|
||||
return;
|
||||
@@ -515,39 +344,46 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
state.transaction_id = transaction_id_;
|
||||
|
||||
std::map<Shard, ExpandOneRequest> per_shard_request_table;
|
||||
auto top_level_rqst_template = request;
|
||||
top_level_rqst_template.transaction_id = transaction_id_;
|
||||
MG_ASSERT(state.requests.size() == 1);
|
||||
auto top_level_rqst = std::move(*state.requests.begin());
|
||||
auto top_level_rqst_template = top_level_rqst;
|
||||
top_level_rqst_template.src_vertices.clear();
|
||||
top_level_rqst_template.edge_types.clear();
|
||||
state.requests.clear();
|
||||
for (auto &vertex : request.src_vertices) {
|
||||
const auto &shard =
|
||||
size_t id = 0;
|
||||
for (const auto &vertex : top_level_rqst.src_vertices) {
|
||||
auto shard =
|
||||
shards_map_.GetShardForKey(vertex.first.id, storage::conversions::ConvertPropertyVector(vertex.second));
|
||||
if (!per_shard_request_table.contains(shard)) {
|
||||
per_shard_request_table.insert(std::pair(shard, top_level_rqst_template));
|
||||
ExpandOneRequest expand_v_rqst = top_level_rqst_template;
|
||||
per_shard_request_table.insert(std::pair(shard, std::move(expand_v_rqst)));
|
||||
state.shard_cache.push_back(shard);
|
||||
}
|
||||
per_shard_request_table[shard].src_vertices.push_back(std::move(vertex));
|
||||
per_shard_request_table[shard].src_vertices.push_back(vertex);
|
||||
per_shard_request_table[shard].edge_types.push_back(top_level_rqst.edge_types[id]);
|
||||
++id;
|
||||
}
|
||||
|
||||
for (auto &[shard, rqst] : per_shard_request_table) {
|
||||
state.shard_cache.push_back(shard);
|
||||
state.requests.push_back(std::move(rqst));
|
||||
}
|
||||
state.state = ExecutionState<ExpandOneRequest>::EXECUTING;
|
||||
}
|
||||
|
||||
StorageClient &GetStorageClientForShard(Shard shard) {
|
||||
if (!storage_cli_manager_.Exists(shard)) {
|
||||
AddStorageClientToManager(shard);
|
||||
StorageClient &GetStorageClientForShard(Shard shard, LabelId label_id) {
|
||||
if (!storage_cli_manager_.Exists(label_id, shard)) {
|
||||
AddStorageClientToManager(shard, label_id);
|
||||
}
|
||||
return storage_cli_manager_.GetClient(shard);
|
||||
return storage_cli_manager_.GetClient(label_id, shard);
|
||||
}
|
||||
|
||||
StorageClient &GetStorageClientForShard(const std::string &label, const CompoundKey &key) {
|
||||
auto shard = shards_map_.GetShardForKey(label, key);
|
||||
return GetStorageClientForShard(std::move(shard));
|
||||
auto label_id = shards_map_.GetLabelId(label);
|
||||
return GetStorageClientForShard(std::move(shard), label_id);
|
||||
}
|
||||
|
||||
void AddStorageClientToManager(Shard target_shard) {
|
||||
void AddStorageClientToManager(Shard target_shard, const LabelId &label_id) {
|
||||
MG_ASSERT(!target_shard.empty());
|
||||
auto leader_addr = target_shard.front();
|
||||
std::vector<Address> addresses;
|
||||
@@ -556,198 +392,10 @@ class ShardRequestManager : public ShardRequestManagerInterface {
|
||||
addresses.push_back(std::move(address.address));
|
||||
}
|
||||
auto cli = StorageClient(io_, std::move(leader_addr.address), std::move(addresses));
|
||||
storage_cli_manager_.AddClient(target_shard, std::move(cli));
|
||||
}
|
||||
|
||||
void SendAllRequests(ExecutionState<ScanVerticesRequest> &state) {
|
||||
int64_t shard_idx = 0;
|
||||
for (const auto &request : state.requests) {
|
||||
const auto ¤t_shard = state.shard_cache[shard_idx];
|
||||
|
||||
auto &storage_client = GetStorageClientForShard(current_shard);
|
||||
ReadRequests req = request;
|
||||
storage_client.SendAsyncReadRequest(request);
|
||||
|
||||
++shard_idx;
|
||||
}
|
||||
}
|
||||
|
||||
void SendAllRequests(ExecutionState<CreateVerticesRequest> &state,
|
||||
std::vector<memgraph::coordinator::Shard> &shard_cache_ref) {
|
||||
size_t id = 0;
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++shard_it) {
|
||||
// This is fine because all new_vertices of each request end up on the same shard
|
||||
const auto labels = state.requests[id].new_vertices[0].label_ids;
|
||||
auto req_deep_copy = state.requests[id];
|
||||
|
||||
for (auto &new_vertex : req_deep_copy.new_vertices) {
|
||||
new_vertex.label_ids.erase(new_vertex.label_ids.begin());
|
||||
}
|
||||
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
|
||||
WriteRequests req = req_deep_copy;
|
||||
storage_client.SendAsyncWriteRequest(req);
|
||||
++id;
|
||||
}
|
||||
}
|
||||
|
||||
void SendAllRequests(ExecutionState<ExpandOneRequest> &state,
|
||||
std::vector<memgraph::coordinator::Shard> &shard_cache_ref) {
|
||||
size_t id = 0;
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end(); ++shard_it) {
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
ReadRequests req = state.requests[id];
|
||||
storage_client.SendAsyncReadRequest(req);
|
||||
++id;
|
||||
}
|
||||
}
|
||||
|
||||
void AwaitOnResponses(ExecutionState<CreateVerticesRequest> &state, std::vector<CreateVerticesResponse> &responses) {
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
int64_t request_idx = 0;
|
||||
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) {
|
||||
// This is fine because all new_vertices of each request end up on the same shard
|
||||
const auto labels = state.requests[request_idx].new_vertices[0].label_ids;
|
||||
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
|
||||
auto poll_result = storage_client.AwaitAsyncWriteRequest();
|
||||
if (!poll_result) {
|
||||
++shard_it;
|
||||
++request_idx;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poll_result->HasError()) {
|
||||
throw std::runtime_error("CreateVertices request timed out");
|
||||
}
|
||||
|
||||
WriteResponses response_variant = poll_result->GetValue();
|
||||
auto response = std::get<CreateVerticesResponse>(response_variant);
|
||||
|
||||
if (!response.success) {
|
||||
throw std::runtime_error("CreateVertices request did not succeed");
|
||||
}
|
||||
responses.push_back(response);
|
||||
|
||||
shard_it = shard_cache_ref.erase(shard_it);
|
||||
// Needed to maintain the 1-1 mapping between the ShardCache and the requests.
|
||||
auto it = state.requests.begin() + request_idx;
|
||||
state.requests.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AwaitOnResponses(ExecutionState<ExpandOneRequest> &state, std::vector<ExpandOneResponse> &responses) {
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
int64_t request_idx = 0;
|
||||
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) {
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
|
||||
auto poll_result = storage_client.PollAsyncReadRequest();
|
||||
if (!poll_result) {
|
||||
++shard_it;
|
||||
++request_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (poll_result->HasError()) {
|
||||
throw std::runtime_error("ExpandOne request timed out");
|
||||
}
|
||||
|
||||
ReadResponses response_variant = poll_result->GetValue();
|
||||
auto response = std::get<ExpandOneResponse>(response_variant);
|
||||
// -NOTE-
|
||||
// Currently a boolean flag for signaling the overall success of the
|
||||
// ExpandOne request does not exist. But it should, so here we assume
|
||||
// that it is already in place.
|
||||
if (!response.success) {
|
||||
throw std::runtime_error("ExpandOne request did not succeed");
|
||||
}
|
||||
|
||||
responses.push_back(std::move(response));
|
||||
shard_it = shard_cache_ref.erase(shard_it);
|
||||
// Needed to maintain the 1-1 mapping between the ShardCache and the requests.
|
||||
auto it = state.requests.begin() + request_idx;
|
||||
state.requests.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AwaitOnPaginatedRequests(ExecutionState<ScanVerticesRequest> &state,
|
||||
std::vector<ScanVerticesResponse> &responses,
|
||||
std::map<Shard, PaginatedResponseState> &paginated_response_tracker) {
|
||||
auto &shard_cache_ref = state.shard_cache;
|
||||
|
||||
// Find the first request that is not holding a paginated response.
|
||||
int64_t request_idx = 0;
|
||||
for (auto shard_it = shard_cache_ref.begin(); shard_it != shard_cache_ref.end();) {
|
||||
if (paginated_response_tracker.at(*shard_it) != PaginatedResponseState::Pending) {
|
||||
++shard_it;
|
||||
++request_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &storage_client = GetStorageClientForShard(*shard_it);
|
||||
|
||||
auto await_result = storage_client.AwaitAsyncReadRequest();
|
||||
|
||||
if (!await_result) {
|
||||
// Redirection has occured.
|
||||
++shard_it;
|
||||
++request_idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await_result->HasError()) {
|
||||
throw std::runtime_error("ScanAll request timed out");
|
||||
}
|
||||
|
||||
ReadResponses read_response_variant = await_result->GetValue();
|
||||
auto response = std::get<ScanVerticesResponse>(read_response_variant);
|
||||
if (!response.success) {
|
||||
throw std::runtime_error("ScanAll request did not succeed");
|
||||
}
|
||||
|
||||
if (!response.next_start_id) {
|
||||
paginated_response_tracker.erase((*shard_it));
|
||||
shard_cache_ref.erase(shard_it);
|
||||
// Needed to maintain the 1-1 mapping between the ShardCache and the requests.
|
||||
auto it = state.requests.begin() + request_idx;
|
||||
state.requests.erase(it);
|
||||
|
||||
} else {
|
||||
state.requests[request_idx].start_id.second = response.next_start_id->second;
|
||||
paginated_response_tracker[*shard_it] = PaginatedResponseState::PartiallyFinished;
|
||||
}
|
||||
responses.push_back(std::move(response));
|
||||
}
|
||||
}
|
||||
|
||||
void SetUpNameIdMappers() {
|
||||
std::unordered_map<uint64_t, std::string> id_to_name;
|
||||
for (const auto &[name, id] : shards_map_.labels) {
|
||||
id_to_name.emplace(id.AsUint(), name);
|
||||
}
|
||||
labels_.StoreMapping(std::move(id_to_name));
|
||||
id_to_name.clear();
|
||||
for (const auto &[name, id] : shards_map_.properties) {
|
||||
id_to_name.emplace(id.AsUint(), name);
|
||||
}
|
||||
properties_.StoreMapping(std::move(id_to_name));
|
||||
id_to_name.clear();
|
||||
for (const auto &[name, id] : shards_map_.edge_types) {
|
||||
id_to_name.emplace(id.AsUint(), name);
|
||||
}
|
||||
edge_types_.StoreMapping(std::move(id_to_name));
|
||||
storage_cli_manager_.AddClient(label_id, target_shard, std::move(cli));
|
||||
}
|
||||
|
||||
ShardMap shards_map_;
|
||||
storage::v3::NameIdMapper properties_;
|
||||
storage::v3::NameIdMapper edge_types_;
|
||||
storage::v3::NameIdMapper labels_;
|
||||
CoordinatorClient coord_cli_;
|
||||
RsmStorageClientManager<StorageClient> storage_cli_manager_;
|
||||
memgraph::io::Io<TTransport> io_;
|
||||
|
||||
45
src/query/v2/stream/common.cpp
Normal file
45
src/query/v2/stream/common.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/stream/common.hpp"
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
namespace memgraph::query::v2::stream {
|
||||
namespace {
|
||||
const std::string kBatchIntervalKey{"batch_interval"};
|
||||
const std::string kBatchSizeKey{"batch_size"};
|
||||
const std::string kTransformationName{"transformation_name"};
|
||||
} // namespace
|
||||
|
||||
void to_json(nlohmann::json &data, CommonStreamInfo &&common_info) {
|
||||
data[kBatchIntervalKey] = common_info.batch_interval.count();
|
||||
data[kBatchSizeKey] = common_info.batch_size;
|
||||
data[kTransformationName] = common_info.transformation_name;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &data, CommonStreamInfo &common_info) {
|
||||
if (const auto batch_interval = data.at(kBatchIntervalKey); !batch_interval.is_null()) {
|
||||
using BatchInterval = decltype(common_info.batch_interval);
|
||||
common_info.batch_interval = BatchInterval{batch_interval.get<typename BatchInterval::rep>()};
|
||||
} else {
|
||||
common_info.batch_interval = kDefaultBatchInterval;
|
||||
}
|
||||
|
||||
if (const auto batch_size = data.at(kBatchSizeKey); !batch_size.is_null()) {
|
||||
common_info.batch_size = batch_size.get<decltype(common_info.batch_size)>();
|
||||
} else {
|
||||
common_info.batch_size = kDefaultBatchSize;
|
||||
}
|
||||
|
||||
data.at(kTransformationName).get_to(common_info.transformation_name);
|
||||
}
|
||||
} // namespace memgraph::query::v2::stream
|
||||
87
src/query/v2/stream/common.hpp
Normal file
87
src/query/v2/stream/common.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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 <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
|
||||
namespace memgraph::query::v2::stream {
|
||||
|
||||
inline constexpr std::chrono::milliseconds kDefaultBatchInterval{100};
|
||||
inline constexpr int64_t kDefaultBatchSize{1000};
|
||||
|
||||
template <typename TMessage>
|
||||
using ConsumerFunction = std::function<void(const std::vector<TMessage> &)>;
|
||||
|
||||
struct CommonStreamInfo {
|
||||
std::chrono::milliseconds batch_interval;
|
||||
int64_t batch_size;
|
||||
std::string transformation_name;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ConvertableToJson = requires(T value, nlohmann::json data) {
|
||||
{ to_json(data, std::move(value)) } -> std::same_as<void>;
|
||||
{ from_json(data, value) } -> std::same_as<void>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept ConvertableToMgpMessage = requires(T value) {
|
||||
mgp_message{value};
|
||||
};
|
||||
|
||||
template <typename TStream>
|
||||
concept Stream = requires(TStream stream) {
|
||||
typename TStream::StreamInfo;
|
||||
typename TStream::Message;
|
||||
TStream{std::string{""}, typename TStream::StreamInfo{}, ConsumerFunction<typename TStream::Message>{}};
|
||||
{ stream.Start() } -> std::same_as<void>;
|
||||
{ stream.StartWithLimit(uint64_t{}, std::optional<std::chrono::milliseconds>{}) } -> std::same_as<void>;
|
||||
{ stream.Stop() } -> std::same_as<void>;
|
||||
{ stream.IsRunning() } -> std::same_as<bool>;
|
||||
{
|
||||
stream.Check(std::optional<std::chrono::milliseconds>{}, std::optional<uint64_t>{},
|
||||
ConsumerFunction<typename TStream::Message>{})
|
||||
} -> std::same_as<void>;
|
||||
requires std::same_as<std::decay_t<decltype(std::declval<typename TStream::StreamInfo>().common_info)>,
|
||||
CommonStreamInfo>;
|
||||
|
||||
requires ConvertableToMgpMessage<typename TStream::Message>;
|
||||
requires ConvertableToJson<typename TStream::StreamInfo>;
|
||||
};
|
||||
|
||||
enum class StreamSourceType : uint8_t { KAFKA, PULSAR };
|
||||
|
||||
constexpr std::string_view StreamSourceTypeToString(StreamSourceType type) {
|
||||
switch (type) {
|
||||
case StreamSourceType::KAFKA:
|
||||
return "kafka";
|
||||
case StreamSourceType::PULSAR:
|
||||
return "pulsar";
|
||||
}
|
||||
}
|
||||
|
||||
template <Stream T>
|
||||
StreamSourceType StreamType(const T & /*stream*/);
|
||||
|
||||
const std::string kCommonInfoKey = "common_info";
|
||||
|
||||
void to_json(nlohmann::json &data, CommonStreamInfo &&info);
|
||||
void from_json(const nlohmann::json &data, CommonStreamInfo &common_info);
|
||||
} // namespace memgraph::query::v2::stream
|
||||
137
src/query/v2/stream/sources.cpp
Normal file
137
src/query/v2/stream/sources.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/stream/sources.hpp"
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "integrations/constants.hpp"
|
||||
|
||||
namespace memgraph::query::v2::stream {
|
||||
KafkaStream::KafkaStream(std::string stream_name, StreamInfo stream_info,
|
||||
ConsumerFunction<integrations::kafka::Message> consumer_function) {
|
||||
integrations::kafka::ConsumerInfo consumer_info{
|
||||
.consumer_name = std::move(stream_name),
|
||||
.topics = std::move(stream_info.topics),
|
||||
.consumer_group = std::move(stream_info.consumer_group),
|
||||
.bootstrap_servers = std::move(stream_info.bootstrap_servers),
|
||||
.batch_interval = stream_info.common_info.batch_interval,
|
||||
.batch_size = stream_info.common_info.batch_size,
|
||||
.public_configs = std::move(stream_info.configs),
|
||||
.private_configs = std::move(stream_info.credentials),
|
||||
};
|
||||
consumer_.emplace(std::move(consumer_info), std::move(consumer_function));
|
||||
};
|
||||
|
||||
KafkaStream::StreamInfo KafkaStream::Info(std::string transformation_name) const {
|
||||
const auto &info = consumer_->Info();
|
||||
return {{.batch_interval = info.batch_interval,
|
||||
.batch_size = info.batch_size,
|
||||
.transformation_name = std::move(transformation_name)},
|
||||
.topics = info.topics,
|
||||
.consumer_group = info.consumer_group,
|
||||
.bootstrap_servers = info.bootstrap_servers,
|
||||
.configs = info.public_configs,
|
||||
.credentials = info.private_configs};
|
||||
}
|
||||
|
||||
void KafkaStream::Start() { consumer_->Start(); }
|
||||
void KafkaStream::StartWithLimit(uint64_t batch_limit, std::optional<std::chrono::milliseconds> timeout) const {
|
||||
consumer_->StartWithLimit(batch_limit, timeout);
|
||||
}
|
||||
void KafkaStream::Stop() { consumer_->Stop(); }
|
||||
bool KafkaStream::IsRunning() const { return consumer_->IsRunning(); }
|
||||
|
||||
void KafkaStream::Check(std::optional<std::chrono::milliseconds> timeout, std::optional<uint64_t> batch_limit,
|
||||
const ConsumerFunction<integrations::kafka::Message> &consumer_function) const {
|
||||
consumer_->Check(timeout, batch_limit, consumer_function);
|
||||
}
|
||||
|
||||
utils::BasicResult<std::string> KafkaStream::SetStreamOffset(const int64_t offset) {
|
||||
return consumer_->SetConsumerOffsets(offset);
|
||||
}
|
||||
|
||||
namespace {
|
||||
const std::string kTopicsKey{"topics"};
|
||||
const std::string kConsumerGroupKey{"consumer_group"};
|
||||
const std::string kBoostrapServers{"bootstrap_servers"};
|
||||
const std::string kConfigs{"configs"};
|
||||
const std::string kCredentials{"credentials"};
|
||||
|
||||
const std::unordered_map<std::string, std::string> kDefaultConfigsMap;
|
||||
} // namespace
|
||||
|
||||
void to_json(nlohmann::json &data, KafkaStream::StreamInfo &&info) {
|
||||
data[kCommonInfoKey] = std::move(info.common_info);
|
||||
data[kTopicsKey] = std::move(info.topics);
|
||||
data[kConsumerGroupKey] = info.consumer_group;
|
||||
data[kBoostrapServers] = std::move(info.bootstrap_servers);
|
||||
data[kConfigs] = std::move(info.configs);
|
||||
data[kCredentials] = std::move(info.credentials);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &data, KafkaStream::StreamInfo &info) {
|
||||
data.at(kCommonInfoKey).get_to(info.common_info);
|
||||
data.at(kTopicsKey).get_to(info.topics);
|
||||
data.at(kConsumerGroupKey).get_to(info.consumer_group);
|
||||
data.at(kBoostrapServers).get_to(info.bootstrap_servers);
|
||||
// These values might not be present in the persisted JSON object
|
||||
info.configs = data.value(kConfigs, kDefaultConfigsMap);
|
||||
info.credentials = data.value(kCredentials, kDefaultConfigsMap);
|
||||
}
|
||||
|
||||
PulsarStream::PulsarStream(std::string stream_name, StreamInfo stream_info,
|
||||
ConsumerFunction<integrations::pulsar::Message> consumer_function) {
|
||||
integrations::pulsar::ConsumerInfo consumer_info{.batch_size = stream_info.common_info.batch_size,
|
||||
.batch_interval = stream_info.common_info.batch_interval,
|
||||
.topics = std::move(stream_info.topics),
|
||||
.consumer_name = std::move(stream_name),
|
||||
.service_url = std::move(stream_info.service_url)};
|
||||
|
||||
consumer_.emplace(std::move(consumer_info), std::move(consumer_function));
|
||||
};
|
||||
|
||||
PulsarStream::StreamInfo PulsarStream::Info(std::string transformation_name) const {
|
||||
const auto &info = consumer_->Info();
|
||||
return {{.batch_interval = info.batch_interval,
|
||||
.batch_size = info.batch_size,
|
||||
.transformation_name = std::move(transformation_name)},
|
||||
.topics = info.topics,
|
||||
.service_url = info.service_url};
|
||||
}
|
||||
|
||||
void PulsarStream::Start() { consumer_->Start(); }
|
||||
void PulsarStream::StartWithLimit(uint64_t batch_limit, std::optional<std::chrono::milliseconds> timeout) const {
|
||||
consumer_->StartWithLimit(batch_limit, timeout);
|
||||
}
|
||||
void PulsarStream::Stop() { consumer_->Stop(); }
|
||||
bool PulsarStream::IsRunning() const { return consumer_->IsRunning(); }
|
||||
void PulsarStream::Check(std::optional<std::chrono::milliseconds> timeout, std::optional<uint64_t> batch_limit,
|
||||
const ConsumerFunction<Message> &consumer_function) const {
|
||||
consumer_->Check(timeout, batch_limit, consumer_function);
|
||||
}
|
||||
|
||||
namespace {
|
||||
const std::string kServiceUrl{"service_url"};
|
||||
} // namespace
|
||||
|
||||
void to_json(nlohmann::json &data, PulsarStream::StreamInfo &&info) {
|
||||
data[kCommonInfoKey] = std::move(info.common_info);
|
||||
data[kTopicsKey] = std::move(info.topics);
|
||||
data[kServiceUrl] = std::move(info.service_url);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &data, PulsarStream::StreamInfo &info) {
|
||||
data.at(kCommonInfoKey).get_to(info.common_info);
|
||||
data.at(kTopicsKey).get_to(info.topics);
|
||||
data.at(kServiceUrl).get_to(info.service_url);
|
||||
}
|
||||
} // namespace memgraph::query::v2::stream
|
||||
95
src/query/v2/stream/sources.hpp
Normal file
95
src/query/v2/stream/sources.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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 "query/v2/stream/common.hpp"
|
||||
|
||||
#include "integrations/kafka/consumer.hpp"
|
||||
#include "integrations/pulsar/consumer.hpp"
|
||||
|
||||
namespace memgraph::query::v2::stream {
|
||||
|
||||
struct KafkaStream {
|
||||
struct StreamInfo {
|
||||
CommonStreamInfo common_info;
|
||||
std::vector<std::string> topics;
|
||||
std::string consumer_group;
|
||||
std::string bootstrap_servers;
|
||||
std::unordered_map<std::string, std::string> configs;
|
||||
std::unordered_map<std::string, std::string> credentials;
|
||||
};
|
||||
|
||||
using Message = integrations::kafka::Message;
|
||||
|
||||
KafkaStream(std::string stream_name, StreamInfo stream_info,
|
||||
ConsumerFunction<integrations::kafka::Message> consumer_function);
|
||||
|
||||
StreamInfo Info(std::string transformation_name) const;
|
||||
|
||||
void Start();
|
||||
void StartWithLimit(uint64_t batch_limit, std::optional<std::chrono::milliseconds> timeout) const;
|
||||
void Stop();
|
||||
bool IsRunning() const;
|
||||
|
||||
void Check(std::optional<std::chrono::milliseconds> timeout, std::optional<uint64_t> batch_limit,
|
||||
const ConsumerFunction<Message> &consumer_function) const;
|
||||
|
||||
utils::BasicResult<std::string> SetStreamOffset(int64_t offset);
|
||||
|
||||
private:
|
||||
using Consumer = integrations::kafka::Consumer;
|
||||
std::optional<Consumer> consumer_;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &data, KafkaStream::StreamInfo &&info);
|
||||
void from_json(const nlohmann::json &data, KafkaStream::StreamInfo &info);
|
||||
|
||||
template <>
|
||||
inline StreamSourceType StreamType(const KafkaStream & /*stream*/) {
|
||||
return StreamSourceType::KAFKA;
|
||||
}
|
||||
|
||||
struct PulsarStream {
|
||||
struct StreamInfo {
|
||||
CommonStreamInfo common_info;
|
||||
std::vector<std::string> topics;
|
||||
std::string service_url;
|
||||
};
|
||||
|
||||
using Message = integrations::pulsar::Message;
|
||||
|
||||
PulsarStream(std::string stream_name, StreamInfo stream_info, ConsumerFunction<Message> consumer_function);
|
||||
|
||||
StreamInfo Info(std::string transformation_name) const;
|
||||
|
||||
void Start();
|
||||
void StartWithLimit(uint64_t batch_limit, std::optional<std::chrono::milliseconds> timeout) const;
|
||||
void Stop();
|
||||
bool IsRunning() const;
|
||||
|
||||
void Check(std::optional<std::chrono::milliseconds> timeout, std::optional<uint64_t> batch_limit,
|
||||
const ConsumerFunction<Message> &consumer_function) const;
|
||||
|
||||
private:
|
||||
using Consumer = integrations::pulsar::Consumer;
|
||||
std::optional<Consumer> consumer_;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &data, PulsarStream::StreamInfo &&info);
|
||||
void from_json(const nlohmann::json &data, PulsarStream::StreamInfo &info);
|
||||
|
||||
template <>
|
||||
inline StreamSourceType StreamType(const PulsarStream & /*stream*/) {
|
||||
return StreamSourceType::PULSAR;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2::stream
|
||||
773
src/query/v2/stream/streams.cpp
Normal file
773
src/query/v2/stream/streams.cpp
Normal file
@@ -0,0 +1,773 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/stream/streams.hpp"
|
||||
|
||||
#include <shared_mutex>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "integrations/constants.hpp"
|
||||
#include "mg_procedure.h"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/discard_value_stream.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/interpreter.hpp"
|
||||
#include "query/v2/procedure/mg_procedure_helpers.hpp"
|
||||
#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
#include "query/v2/procedure/module.hpp"
|
||||
#include "query/v2/stream/sources.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/pmr/string.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
namespace EventCounter {
|
||||
extern const Event MessagesConsumed;
|
||||
} // namespace EventCounter
|
||||
|
||||
namespace memgraph::query::v2::stream {
|
||||
namespace {
|
||||
inline constexpr auto kExpectedTransformationResultSize = 2;
|
||||
inline constexpr auto kCheckStreamResultSize = 2;
|
||||
const utils::pmr::string query_param_name{"query", utils::NewDeleteResource()};
|
||||
const utils::pmr::string params_param_name{"parameters", utils::NewDeleteResource()};
|
||||
|
||||
const std::map<std::string, storage::v3::PropertyValue> empty_parameters{};
|
||||
|
||||
auto GetStream(auto &map, const std::string &stream_name) {
|
||||
if (auto it = map.find(stream_name); it != map.end()) {
|
||||
return it;
|
||||
}
|
||||
throw StreamsException("Couldn't find stream '{}'", stream_name);
|
||||
}
|
||||
|
||||
std::pair<TypedValue /*query*/, TypedValue /*parameters*/> ExtractTransformationResult(
|
||||
const utils::pmr::map<utils::pmr::string, TypedValue> &values, const std::string_view transformation_name,
|
||||
const std::string_view stream_name) {
|
||||
if (values.size() != kExpectedTransformationResultSize) {
|
||||
throw StreamsException(
|
||||
"Transformation '{}' in stream '{}' did not yield all fields (query, parameters) as required.",
|
||||
transformation_name, stream_name);
|
||||
}
|
||||
|
||||
auto get_value = [&](const utils::pmr::string &field_name) mutable -> const TypedValue & {
|
||||
auto it = values.find(field_name);
|
||||
if (it == values.end()) {
|
||||
throw StreamsException{"Transformation '{}' in stream '{}' did not yield a record with '{}' field.",
|
||||
transformation_name, stream_name, field_name};
|
||||
};
|
||||
return it->second;
|
||||
};
|
||||
|
||||
const auto &query_value = get_value(query_param_name);
|
||||
MG_ASSERT(query_value.IsString());
|
||||
const auto ¶ms_value = get_value(params_param_name);
|
||||
MG_ASSERT(params_value.IsNull() || params_value.IsMap());
|
||||
return {query_value, params_value};
|
||||
}
|
||||
|
||||
template <typename TMessage>
|
||||
void CallCustomTransformation(const std::string &transformation_name, const std::vector<TMessage> &messages,
|
||||
mgp_result &result, storage::v3::Shard::Accessor &storage_accessor,
|
||||
utils::MemoryResource &memory_resource, const std::string &stream_name) {
|
||||
DbAccessor db_accessor{&storage_accessor};
|
||||
{
|
||||
auto maybe_transformation =
|
||||
procedure::FindTransformation(procedure::gModuleRegistry, transformation_name, utils::NewDeleteResource());
|
||||
|
||||
if (!maybe_transformation) {
|
||||
throw StreamsException("Couldn't find transformation {} for stream '{}'", transformation_name, stream_name);
|
||||
};
|
||||
const auto &trans = *maybe_transformation->second;
|
||||
mgp_messages mgp_messages{mgp_messages::storage_type{&memory_resource}};
|
||||
std::transform(messages.begin(), messages.end(), std::back_inserter(mgp_messages.messages),
|
||||
[](const TMessage &message) { return mgp_message{message}; });
|
||||
mgp_graph graph{&db_accessor, storage::v3::View::OLD, nullptr};
|
||||
mgp_memory memory{&memory_resource};
|
||||
result.rows.clear();
|
||||
result.error_msg.reset();
|
||||
result.signature = &trans.results;
|
||||
|
||||
MG_ASSERT(result.signature->size() == kExpectedTransformationResultSize);
|
||||
MG_ASSERT(result.signature->contains(query_param_name));
|
||||
MG_ASSERT(result.signature->contains(params_param_name));
|
||||
|
||||
spdlog::trace("Calling transformation in stream '{}'", stream_name);
|
||||
trans.cb(&mgp_messages, &graph, &result, &memory);
|
||||
}
|
||||
if (result.error_msg.has_value()) {
|
||||
throw StreamsException(result.error_msg->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
template <Stream TStream>
|
||||
StreamStatus<TStream> CreateStatus(std::string stream_name, std::string transformation_name,
|
||||
std::optional<std::string> owner, const TStream &stream) {
|
||||
return {.name = std::move(stream_name),
|
||||
.type = StreamType(stream),
|
||||
.is_running = stream.IsRunning(),
|
||||
.info = stream.Info(std::move(transformation_name)),
|
||||
.owner = std::move(owner)};
|
||||
}
|
||||
|
||||
// nlohmann::json doesn't support string_view access yet
|
||||
const std::string kStreamName{"name"};
|
||||
const std::string kIsRunningKey{"is_running"};
|
||||
const std::string kOwner{"owner"};
|
||||
const std::string kType{"type"};
|
||||
} // namespace
|
||||
|
||||
template <Stream TStream>
|
||||
void to_json(nlohmann::json &data, StreamStatus<TStream> &&status) {
|
||||
data[kStreamName] = std::move(status.name);
|
||||
data[kType] = status.type;
|
||||
data[kIsRunningKey] = status.is_running;
|
||||
|
||||
if (status.owner.has_value()) {
|
||||
data[kOwner] = std::move(*status.owner);
|
||||
} else {
|
||||
data[kOwner] = nullptr;
|
||||
}
|
||||
|
||||
to_json(data, std::move(status.info));
|
||||
}
|
||||
|
||||
template <Stream TStream>
|
||||
void from_json(const nlohmann::json &data, StreamStatus<TStream> &status) {
|
||||
data.at(kStreamName).get_to(status.name);
|
||||
data.at(kIsRunningKey).get_to(status.is_running);
|
||||
|
||||
if (const auto &owner = data.at(kOwner); !owner.is_null()) {
|
||||
status.owner = owner.get<typename decltype(status.owner)::value_type>();
|
||||
} else {
|
||||
status.owner = {};
|
||||
}
|
||||
|
||||
from_json(data, status.info);
|
||||
}
|
||||
|
||||
Streams::Streams(InterpreterContext *interpreter_context, std::filesystem::path directory)
|
||||
: interpreter_context_(interpreter_context), storage_(std::move(directory)) {
|
||||
RegisterProcedures();
|
||||
}
|
||||
|
||||
void Streams::RegisterProcedures() {
|
||||
RegisterKafkaProcedures();
|
||||
RegisterPulsarProcedures();
|
||||
}
|
||||
|
||||
void Streams::RegisterKafkaProcedures() {
|
||||
{
|
||||
static constexpr std::string_view proc_name = "kafka_set_stream_offset";
|
||||
auto set_stream_offset = [this](mgp_list *args, mgp_graph * /*graph*/, mgp_result *result,
|
||||
mgp_memory * /*memory*/) {
|
||||
auto *arg_stream_name = procedure::Call<mgp_value *>(mgp_list_at, args, 0);
|
||||
const auto *stream_name = procedure::Call<const char *>(mgp_value_get_string, arg_stream_name);
|
||||
auto *arg_offset = procedure::Call<mgp_value *>(mgp_list_at, args, 1);
|
||||
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);
|
||||
}},
|
||||
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);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
|
||||
{
|
||||
static constexpr std::string_view proc_name = "kafka_stream_info";
|
||||
|
||||
static constexpr std::string_view consumer_group_result_name = "consumer_group";
|
||||
static constexpr std::string_view topics_result_name = "topics";
|
||||
static constexpr std::string_view bootstrap_servers_result_name = "bootstrap_servers";
|
||||
static constexpr std::string_view configs_result_name = "configs";
|
||||
static constexpr std::string_view credentials_result_name = "credentials";
|
||||
|
||||
auto get_stream_info = [this](mgp_list *args, mgp_graph * /*graph*/, mgp_result *result, mgp_memory *memory) {
|
||||
auto *arg_stream_name = procedure::Call<mgp_value *>(mgp_list_at, args, 0);
|
||||
const auto *stream_name = procedure::Call<const char *>(mgp_value_get_string, arg_stream_name);
|
||||
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 info = stream_source_ptr->Info(kafka_stream.transformation_name);
|
||||
mgp_result_record *record{nullptr};
|
||||
if (!procedure::TryOrSetError([&] { return mgp_result_new_record(result, &record); }, result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto consumer_group_value =
|
||||
procedure::GetStringValueOrSetError(info.consumer_group.c_str(), memory, result);
|
||||
if (!consumer_group_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
procedure::MgpUniquePtr<mgp_list> topic_names{nullptr, mgp_list_destroy};
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] {
|
||||
return procedure::CreateMgpObject(topic_names, mgp_list_make_empty, info.topics.size(),
|
||||
memory);
|
||||
},
|
||||
result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &topic : info.topics) {
|
||||
auto topic_value = procedure::GetStringValueOrSetError(topic.c_str(), memory, result);
|
||||
if (!topic_value) {
|
||||
return;
|
||||
}
|
||||
topic_names->elems.push_back(std::move(*topic_value));
|
||||
}
|
||||
|
||||
procedure::MgpUniquePtr<mgp_value> topics_value{nullptr, mgp_value_destroy};
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] {
|
||||
return procedure::CreateMgpObject(topics_value, mgp_value_make_list, topic_names.get());
|
||||
},
|
||||
result)) {
|
||||
return;
|
||||
}
|
||||
static_cast<void>(topic_names.release());
|
||||
|
||||
const auto bootstrap_servers_value =
|
||||
procedure::GetStringValueOrSetError(info.bootstrap_servers.c_str(), memory, result);
|
||||
if (!bootstrap_servers_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto convert_config_map =
|
||||
[result, memory](const std::unordered_map<std::string, std::string> &configs_to_convert)
|
||||
-> procedure::MgpUniquePtr<mgp_value> {
|
||||
procedure::MgpUniquePtr<mgp_value> configs_value{nullptr, mgp_value_destroy};
|
||||
procedure::MgpUniquePtr<mgp_map> configs{nullptr, mgp_map_destroy};
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] { return procedure::CreateMgpObject(configs, mgp_map_make_empty, memory); }, result)) {
|
||||
return configs_value;
|
||||
}
|
||||
|
||||
for (const auto &[key, value] : configs_to_convert) {
|
||||
auto value_value = procedure::GetStringValueOrSetError(value.c_str(), memory, result);
|
||||
if (!value_value) {
|
||||
return configs_value;
|
||||
}
|
||||
configs->items.emplace(key, std::move(*value_value));
|
||||
}
|
||||
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] { return procedure::CreateMgpObject(configs_value, mgp_value_make_map, configs.get()); },
|
||||
result)) {
|
||||
return configs_value;
|
||||
}
|
||||
static_cast<void>(configs.release());
|
||||
return configs_value;
|
||||
};
|
||||
|
||||
const auto configs_value = convert_config_map(info.configs);
|
||||
if (configs_value == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
using CredentialsType = decltype(KafkaStream::StreamInfo::credentials);
|
||||
CredentialsType reducted_credentials;
|
||||
std::transform(info.credentials.begin(), info.credentials.end(),
|
||||
std::inserter(reducted_credentials, reducted_credentials.end()),
|
||||
[](const auto &pair) -> CredentialsType::value_type {
|
||||
return {pair.first, integrations::kReducted};
|
||||
});
|
||||
|
||||
const auto credentials_value = convert_config_map(reducted_credentials);
|
||||
if (credentials_value == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, consumer_group_result_name.data(),
|
||||
consumer_group_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, topics_result_name.data(), topics_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, bootstrap_servers_result_name.data(),
|
||||
bootstrap_servers_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, configs_result_name.data(),
|
||||
configs_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, credentials_result_name.data(),
|
||||
credentials_value.get())) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](auto && /*other*/) {
|
||||
throw QueryRuntimeException("'{}' can be only used for Kafka stream sources", proc_name);
|
||||
}},
|
||||
it->second);
|
||||
};
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
MG_ASSERT(mgp_proc_add_result(&proc, configs_result_name.data(), procedure::Call<mgp_type *>(mgp_type_map)) ==
|
||||
mgp_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);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
}
|
||||
|
||||
void Streams::RegisterPulsarProcedures() {
|
||||
{
|
||||
static constexpr std::string_view proc_name = "pulsar_stream_info";
|
||||
static constexpr std::string_view service_url_result_name = "service_url";
|
||||
static constexpr std::string_view topics_result_name = "topics";
|
||||
auto get_stream_info = [this](mgp_list *args, mgp_graph * /*graph*/, mgp_result *result, mgp_memory *memory) {
|
||||
auto *arg_stream_name = procedure::Call<mgp_value *>(mgp_list_at, args, 0);
|
||||
const auto *stream_name = procedure::Call<const char *>(mgp_value_get_string, arg_stream_name);
|
||||
auto lock_ptr = streams_.Lock();
|
||||
auto it = GetStream(*lock_ptr, std::string(stream_name));
|
||||
std::visit(
|
||||
utils::Overloaded{
|
||||
[&](StreamData<PulsarStream> &pulsar_stream) {
|
||||
auto stream_source_ptr = pulsar_stream.stream_source->Lock();
|
||||
const auto info = stream_source_ptr->Info(pulsar_stream.transformation_name);
|
||||
mgp_result_record *record{nullptr};
|
||||
if (!procedure::TryOrSetError([&] { return mgp_result_new_record(result, &record); }, result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto service_url_value = procedure::GetStringValueOrSetError(info.service_url.c_str(), memory, result);
|
||||
if (!service_url_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
procedure::MgpUniquePtr<mgp_list> topic_names{nullptr, mgp_list_destroy};
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] {
|
||||
return procedure::CreateMgpObject(topic_names, mgp_list_make_empty, info.topics.size(),
|
||||
memory);
|
||||
},
|
||||
result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &topic : info.topics) {
|
||||
auto topic_value = procedure::GetStringValueOrSetError(topic.c_str(), memory, result);
|
||||
if (!topic_value) {
|
||||
return;
|
||||
}
|
||||
topic_names->elems.push_back(std::move(*topic_value));
|
||||
}
|
||||
|
||||
procedure::MgpUniquePtr<mgp_value> topics_value{nullptr, mgp_value_destroy};
|
||||
if (!procedure::TryOrSetError(
|
||||
[&] {
|
||||
return procedure::CreateMgpObject(topics_value, mgp_value_make_list, topic_names.release());
|
||||
},
|
||||
result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, topics_result_name.data(), topics_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!procedure::InsertResultOrSetError(result, record, service_url_result_name.data(),
|
||||
service_url_value.get())) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](auto && /*other*/) {
|
||||
throw QueryRuntimeException("'{}' can be only used for Pulsar stream sources", proc_name);
|
||||
}},
|
||||
it->second);
|
||||
};
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
procedure::gModuleRegistry.RegisterMgProcedure(proc_name, std::move(proc));
|
||||
}
|
||||
}
|
||||
|
||||
template <Stream TStream>
|
||||
void Streams::Create(const std::string &stream_name, typename TStream::StreamInfo info,
|
||||
std::optional<std::string> owner) {
|
||||
auto locked_streams = streams_.Lock();
|
||||
auto it = CreateConsumer<TStream>(*locked_streams, stream_name, std::move(info), std::move(owner));
|
||||
|
||||
try {
|
||||
std::visit(
|
||||
[&](const auto &stream_data) {
|
||||
const auto stream_source_ptr = stream_data.stream_source->ReadLock();
|
||||
Persist(CreateStatus(stream_name, stream_data.transformation_name, stream_data.owner, *stream_source_ptr));
|
||||
},
|
||||
it->second);
|
||||
} catch (...) {
|
||||
locked_streams->erase(it);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template void Streams::Create<KafkaStream>(const std::string &stream_name, KafkaStream::StreamInfo info,
|
||||
std::optional<std::string> owner);
|
||||
template void Streams::Create<PulsarStream>(const std::string &stream_name, PulsarStream::StreamInfo info,
|
||||
std::optional<std::string> owner);
|
||||
|
||||
template <Stream TStream>
|
||||
Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std::string &stream_name,
|
||||
typename TStream::StreamInfo stream_info,
|
||||
std::optional<std::string> owner) {
|
||||
if (map.contains(stream_name)) {
|
||||
throw StreamsException{"Stream already exists with name '{}'", stream_name};
|
||||
}
|
||||
|
||||
auto *memory_resource = utils::NewDeleteResource();
|
||||
|
||||
auto consumer_function = [interpreter_context = interpreter_context_, memory_resource, stream_name,
|
||||
transformation_name = stream_info.common_info.transformation_name, owner = owner,
|
||||
interpreter = std::make_shared<Interpreter>(interpreter_context_),
|
||||
result = mgp_result{nullptr, memory_resource},
|
||||
total_retries = interpreter_context_->config.stream_transaction_conflict_retries,
|
||||
retry_interval = interpreter_context_->config.stream_transaction_retry_interval](
|
||||
const std::vector<typename TStream::Message> &messages) mutable {
|
||||
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
|
||||
EventCounter::IncrementCounter(EventCounter::MessagesConsumed, messages.size());
|
||||
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
|
||||
|
||||
DiscardValueResultStream stream;
|
||||
|
||||
spdlog::trace("Start transaction in stream '{}'", stream_name);
|
||||
utils::OnScopeExit cleanup{[&interpreter, &result]() {
|
||||
result.rows.clear();
|
||||
interpreter->Abort();
|
||||
}};
|
||||
|
||||
const static std::map<std::string, storage::v3::PropertyValue> empty_parameters{};
|
||||
uint32_t i = 0;
|
||||
while (true) {
|
||||
try {
|
||||
interpreter->BeginTransaction();
|
||||
for (auto &row : result.rows) {
|
||||
spdlog::trace("Processing row in stream '{}'", stream_name);
|
||||
auto [query_value, params_value] = ExtractTransformationResult(row.values, transformation_name, stream_name);
|
||||
storage::v3::PropertyValue params_prop = storage::v3::TypedToPropertyValue(params_value);
|
||||
|
||||
std::string query{query_value.ValueString()};
|
||||
spdlog::trace("Executing query '{}' in stream '{}'", query, stream_name);
|
||||
auto prepare_result =
|
||||
interpreter->Prepare(query, params_prop.IsNull() ? empty_parameters : params_prop.ValueMap(), nullptr);
|
||||
if (!interpreter_context->auth_checker->IsUserAuthorized(owner, prepare_result.privileges)) {
|
||||
throw StreamsException{
|
||||
"Couldn't execute query '{}' for stream '{}' because the owner is not authorized to execute the "
|
||||
"query!",
|
||||
query, stream_name};
|
||||
}
|
||||
interpreter->PullAll(&stream);
|
||||
}
|
||||
|
||||
spdlog::trace("Commit transaction in stream '{}'", stream_name);
|
||||
interpreter->CommitTransaction();
|
||||
result.rows.clear();
|
||||
break;
|
||||
} catch (const query::v2::TransactionSerializationException &e) {
|
||||
interpreter->Abort();
|
||||
if (i == total_retries) {
|
||||
throw;
|
||||
}
|
||||
++i;
|
||||
std::this_thread::sleep_for(retry_interval);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto insert_result = map.try_emplace(
|
||||
stream_name, StreamData<TStream>{std::move(stream_info.common_info.transformation_name), std::move(owner),
|
||||
std::make_unique<SynchronizedStreamSource<TStream>>(
|
||||
stream_name, std::move(stream_info), std::move(consumer_function))});
|
||||
MG_ASSERT(insert_result.second, "Unexpected error during storing consumer '{}'", stream_name);
|
||||
return insert_result.first;
|
||||
}
|
||||
|
||||
void Streams::RestoreStreams() {
|
||||
spdlog::info("Loading streams...");
|
||||
auto locked_streams_map = streams_.Lock();
|
||||
MG_ASSERT(locked_streams_map->empty(), "Cannot restore streams when some streams already exist!");
|
||||
|
||||
for (const auto &[stream_name, stream_data] : storage_) {
|
||||
const auto get_failed_message = [&stream_name = stream_name](const std::string_view message,
|
||||
const std::string_view nested_message) {
|
||||
return fmt::format("Failed to load stream '{}', because: {} caused by {}", stream_name, message, nested_message);
|
||||
};
|
||||
|
||||
const auto create_consumer = [&, &stream_name = stream_name, this]<typename T>(StreamStatus<T> status,
|
||||
auto &&stream_json_data) {
|
||||
try {
|
||||
stream_json_data.get_to(status);
|
||||
} catch (const nlohmann::json::type_error &exception) {
|
||||
spdlog::warn(get_failed_message("invalid type conversion", exception.what()));
|
||||
return;
|
||||
} catch (const nlohmann::json::out_of_range &exception) {
|
||||
spdlog::warn(get_failed_message("non existing field", exception.what()));
|
||||
return;
|
||||
}
|
||||
MG_ASSERT(status.name == stream_name, "Expected stream name is '{}', but got '{}'", status.name, stream_name);
|
||||
|
||||
try {
|
||||
auto it = CreateConsumer<T>(*locked_streams_map, stream_name, std::move(status.info), std::move(status.owner));
|
||||
if (status.is_running) {
|
||||
std::visit(
|
||||
[&](const auto &stream_data) {
|
||||
auto stream_source_ptr = stream_data.stream_source->Lock();
|
||||
stream_source_ptr->Start();
|
||||
},
|
||||
it->second);
|
||||
}
|
||||
spdlog::info("Stream '{}' is loaded", stream_name);
|
||||
} catch (const utils::BasicException &exception) {
|
||||
spdlog::warn(get_failed_message("unexpected error", exception.what()));
|
||||
}
|
||||
};
|
||||
|
||||
auto stream_json_data = nlohmann::json::parse(stream_data);
|
||||
if (const auto it = stream_json_data.find(kType); it != stream_json_data.end()) {
|
||||
const auto stream_type = static_cast<StreamSourceType>(*it);
|
||||
switch (stream_type) {
|
||||
case StreamSourceType::KAFKA:
|
||||
create_consumer(StreamStatus<KafkaStream>{}, std::move(stream_json_data));
|
||||
break;
|
||||
case StreamSourceType::PULSAR:
|
||||
create_consumer(StreamStatus<PulsarStream>{}, std::move(stream_json_data));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
spdlog::warn(
|
||||
"Unable to load stream '{}', because it does not contain the type of the stream. Most probably the stream "
|
||||
"was saved before Memgraph 2.1. Please recreate the stream manually to make it work. For more information "
|
||||
"please check https://memgraph.com/docs/memgraph/changelog#v210---nov-22-2021 .",
|
||||
stream_json_data.value(kStreamName, "<invalid format>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Streams::Drop(const std::string &stream_name) {
|
||||
auto locked_streams = streams_.Lock();
|
||||
|
||||
auto it = GetStream(*locked_streams, stream_name);
|
||||
|
||||
// streams_ is write locked, which means there is no access to it outside of this function, thus only the Test
|
||||
// function can be executing with the consumer, nothing else.
|
||||
// By acquiring the write lock here for the consumer, we make sure there is
|
||||
// no running Test function for this consumer, therefore it can be erased.
|
||||
std::visit([&](const auto &stream_data) { stream_data.stream_source->Lock(); }, it->second);
|
||||
|
||||
locked_streams->erase(it);
|
||||
if (!storage_.Delete(stream_name)) {
|
||||
throw StreamsException("Couldn't delete stream '{}' from persistent store!", stream_name);
|
||||
}
|
||||
|
||||
// TODO(antaljanosbenjamin) Release the transformation
|
||||
}
|
||||
|
||||
void Streams::Start(const std::string &stream_name) {
|
||||
auto locked_streams = streams_.Lock();
|
||||
auto it = GetStream(*locked_streams, stream_name);
|
||||
|
||||
std::visit(
|
||||
[&, this](const auto &stream_data) {
|
||||
auto stream_source_ptr = stream_data.stream_source->Lock();
|
||||
stream_source_ptr->Start();
|
||||
Persist(CreateStatus(stream_name, stream_data.transformation_name, stream_data.owner, *stream_source_ptr));
|
||||
},
|
||||
it->second);
|
||||
}
|
||||
|
||||
void Streams::StartWithLimit(const std::string &stream_name, uint64_t batch_limit,
|
||||
std::optional<std::chrono::milliseconds> timeout) const {
|
||||
std::optional locked_streams{streams_.ReadLock()};
|
||||
auto it = GetStream(**locked_streams, stream_name);
|
||||
|
||||
std::visit(
|
||||
[&](const auto &stream_data) {
|
||||
const auto locked_stream_source = stream_data.stream_source->ReadLock();
|
||||
locked_streams.reset();
|
||||
|
||||
locked_stream_source->StartWithLimit(batch_limit, timeout);
|
||||
},
|
||||
it->second);
|
||||
}
|
||||
|
||||
void Streams::Stop(const std::string &stream_name) {
|
||||
auto locked_streams = streams_.Lock();
|
||||
auto it = GetStream(*locked_streams, stream_name);
|
||||
|
||||
std::visit(
|
||||
[&, this](const auto &stream_data) {
|
||||
auto stream_source_ptr = stream_data.stream_source->Lock();
|
||||
stream_source_ptr->Stop();
|
||||
|
||||
Persist(CreateStatus(stream_name, stream_data.transformation_name, stream_data.owner, *stream_source_ptr));
|
||||
},
|
||||
it->second);
|
||||
}
|
||||
|
||||
void Streams::StartAll() {
|
||||
for (auto locked_streams = streams_.Lock(); auto &[stream_name, stream_data] : *locked_streams) {
|
||||
std::visit(
|
||||
[&stream_name = stream_name, this](const auto &stream_data) {
|
||||
auto locked_stream_source = stream_data.stream_source->Lock();
|
||||
if (!locked_stream_source->IsRunning()) {
|
||||
locked_stream_source->Start();
|
||||
Persist(
|
||||
CreateStatus(stream_name, stream_data.transformation_name, stream_data.owner, *locked_stream_source));
|
||||
}
|
||||
},
|
||||
stream_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Streams::StopAll() {
|
||||
for (auto locked_streams = streams_.Lock(); auto &[stream_name, stream_data] : *locked_streams) {
|
||||
std::visit(
|
||||
[&stream_name = stream_name, this](const auto &stream_data) {
|
||||
auto locked_stream_source = stream_data.stream_source->Lock();
|
||||
if (locked_stream_source->IsRunning()) {
|
||||
locked_stream_source->Stop();
|
||||
Persist(
|
||||
CreateStatus(stream_name, stream_data.transformation_name, stream_data.owner, *locked_stream_source));
|
||||
}
|
||||
},
|
||||
stream_data);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<StreamStatus<>> Streams::GetStreamInfo() const {
|
||||
std::vector<StreamStatus<>> result;
|
||||
{
|
||||
for (auto locked_streams = streams_.ReadLock(); const auto &[stream_name, stream_data] : *locked_streams) {
|
||||
std::visit(
|
||||
[&, &stream_name = stream_name](const auto &stream_data) {
|
||||
auto locked_stream_source = stream_data.stream_source->ReadLock();
|
||||
auto info = locked_stream_source->Info(stream_data.transformation_name);
|
||||
result.emplace_back(StreamStatus<>{stream_name, StreamType(*locked_stream_source),
|
||||
locked_stream_source->IsRunning(), std::move(info.common_info),
|
||||
stream_data.owner});
|
||||
},
|
||||
stream_data);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TransformationResult Streams::Check(const std::string &stream_name, std::optional<std::chrono::milliseconds> timeout,
|
||||
std::optional<uint64_t> batch_limit) const {
|
||||
std::optional locked_streams{streams_.ReadLock()};
|
||||
auto it = GetStream(**locked_streams, stream_name);
|
||||
|
||||
return std::visit(
|
||||
[&](const auto &stream_data) {
|
||||
// This depends on the fact that Drop will first acquire a write lock to the consumer, and erase it only after
|
||||
// that
|
||||
const auto locked_stream_source = stream_data.stream_source->ReadLock();
|
||||
const auto transformation_name = stream_data.transformation_name;
|
||||
locked_streams.reset();
|
||||
|
||||
auto *memory_resource = utils::NewDeleteResource();
|
||||
mgp_result result{nullptr, memory_resource};
|
||||
TransformationResult test_result;
|
||||
|
||||
auto consumer_function = [interpreter_context = interpreter_context_, memory_resource, &stream_name,
|
||||
&transformation_name = transformation_name, &result,
|
||||
&test_result]<typename T>(const std::vector<T> &messages) mutable {
|
||||
auto accessor = interpreter_context->db->Access(coordinator::Hlc{});
|
||||
CallCustomTransformation(transformation_name, messages, result, accessor, *memory_resource, stream_name);
|
||||
|
||||
auto result_row = std::vector<TypedValue>();
|
||||
result_row.reserve(kCheckStreamResultSize);
|
||||
|
||||
auto queries_and_parameters = std::vector<TypedValue>(result.rows.size());
|
||||
std::transform(
|
||||
result.rows.cbegin(), result.rows.cend(), queries_and_parameters.begin(), [&](const auto &row) {
|
||||
auto [query, parameters] = ExtractTransformationResult(row.values, transformation_name, stream_name);
|
||||
|
||||
return std::map<std::string, TypedValue>{{"query", std::move(query)},
|
||||
{"parameters", std::move(parameters)}};
|
||||
});
|
||||
result_row.emplace_back(std::move(queries_and_parameters));
|
||||
|
||||
auto messages_list = std::vector<TypedValue>(messages.size());
|
||||
std::transform(messages.cbegin(), messages.cend(), messages_list.begin(), [](const auto &message) {
|
||||
return std::string_view(message.Payload().data(), message.Payload().size());
|
||||
});
|
||||
|
||||
result_row.emplace_back(std::move(messages_list));
|
||||
|
||||
test_result.emplace_back(std::move(result_row));
|
||||
};
|
||||
|
||||
locked_stream_source->Check(timeout, batch_limit, consumer_function);
|
||||
return test_result;
|
||||
},
|
||||
it->second);
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2::stream
|
||||
206
src/query/v2/stream/streams.hpp
Normal file
206
src/query/v2/stream/streams.hpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// 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 <concepts>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "integrations/kafka/consumer.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/stream/common.hpp"
|
||||
#include "query/v2/stream/sources.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
class StreamsTest;
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
struct InterpreterContext;
|
||||
|
||||
namespace stream {
|
||||
|
||||
class StreamsException : public utils::BasicException {
|
||||
public:
|
||||
using BasicException::BasicException;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct StreamInfo;
|
||||
|
||||
template <>
|
||||
struct StreamInfo<void> {
|
||||
using Type = CommonStreamInfo;
|
||||
};
|
||||
|
||||
template <Stream TStream>
|
||||
struct StreamInfo<TStream> {
|
||||
using Type = typename TStream::StreamInfo;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using StreamInfoType = typename StreamInfo<T>::Type;
|
||||
|
||||
template <typename T = void>
|
||||
struct StreamStatus {
|
||||
std::string name;
|
||||
StreamSourceType type;
|
||||
bool is_running;
|
||||
StreamInfoType<T> info;
|
||||
std::optional<std::string> owner;
|
||||
};
|
||||
|
||||
using TransformationResult = std::vector<std::vector<TypedValue>>;
|
||||
|
||||
/// Manages Kafka consumers.
|
||||
///
|
||||
/// This class is responsible for all query supported actions to happen.
|
||||
class Streams final {
|
||||
friend StreamsTest;
|
||||
|
||||
public:
|
||||
/// Initializes the streams.
|
||||
///
|
||||
/// @param interpreter_context context to use to run the result of transformations
|
||||
/// @param directory a directory path to store the persisted streams metadata
|
||||
Streams(InterpreterContext *interpreter_context, std::filesystem::path directory);
|
||||
|
||||
/// Restores the streams from the persisted metadata.
|
||||
/// The restoration is done in a best effort manner, therefore no exception is thrown on failure, but the error is
|
||||
/// logged. If a stream was running previously, then after restoration it will be started.
|
||||
/// This function should only be called when there are no existing streams.
|
||||
void RestoreStreams();
|
||||
|
||||
/// Creates a new import stream.
|
||||
/// The create implies connecting to the server to get metadata necessary to initialize the stream. This
|
||||
/// method assures there is no other stream with the same name.
|
||||
///
|
||||
/// @param stream_name the name of the stream which can be used to uniquely identify the stream
|
||||
/// @param stream_info the necessary informations needed to create the Kafka consumer and transform the messages
|
||||
///
|
||||
/// @throws StreamsException if the stream with the same name exists or if the creation of Kafka consumer fails
|
||||
template <Stream TStream>
|
||||
void Create(const std::string &stream_name, typename TStream::StreamInfo info, std::optional<std::string> owner);
|
||||
|
||||
/// Deletes an existing stream and all the data that was persisted.
|
||||
///
|
||||
/// @param stream_name name of the stream that needs to be deleted.
|
||||
///
|
||||
/// @throws StreamsException if the stream doesn't exist or if the persisted metadata can't be deleted.
|
||||
void Drop(const std::string &stream_name);
|
||||
|
||||
/// Start consuming from a stream.
|
||||
///
|
||||
/// @param stream_name name of the stream that needs to be started
|
||||
///
|
||||
/// @throws StreamsException if the stream doesn't exist or if the metadata cannot be persisted
|
||||
/// @throws ConsumerRunningException if the consumer is already running
|
||||
void Start(const std::string &stream_name);
|
||||
|
||||
/// Start consuming from a stream.
|
||||
///
|
||||
/// @param stream_name name of the stream that needs to be started
|
||||
/// @param batch_limit number of batches we want to consume before stopping
|
||||
/// @param timeout the maximum duration during which the command should run.
|
||||
///
|
||||
/// @throws StreamsException if the stream doesn't exist
|
||||
/// @throws ConsumerRunningException if the consumer is already running
|
||||
void StartWithLimit(const std::string &stream_name, uint64_t batch_limit,
|
||||
std::optional<std::chrono::milliseconds> timeout) const;
|
||||
|
||||
/// Stop consuming from a stream.
|
||||
///
|
||||
/// @param stream_name name of the stream that needs to be stopped
|
||||
///
|
||||
/// @throws StreamsException if the stream doesn't exist or if the metadata cannot be persisted
|
||||
/// @throws ConsumerStoppedException if the consumer is already stopped
|
||||
void Stop(const std::string &stream_name);
|
||||
|
||||
/// Start consuming from all streams that are stopped.
|
||||
///
|
||||
/// @throws StreamsException if the metadata cannot be persisted
|
||||
void StartAll();
|
||||
|
||||
/// Stop consuming from all streams that are running.
|
||||
///
|
||||
/// @throws StreamsException if the metadata cannot be persisted
|
||||
void StopAll();
|
||||
|
||||
/// Return current status for all streams.
|
||||
/// It might happend that the is_running field is out of date if the one of the streams stops during the invocation of
|
||||
/// this function because of an error.
|
||||
std::vector<StreamStatus<>> GetStreamInfo() const;
|
||||
|
||||
/// Do a dry-run consume from a stream.
|
||||
///
|
||||
/// @param stream_name name of the stream we want to test
|
||||
/// @param batch_limit number of batches we want to test before stopping
|
||||
/// @param timeout the maximum duration during which the command should run.
|
||||
///
|
||||
/// @returns A vector of vectors of TypedValue. Each subvector contains two elements, the query string and the
|
||||
/// nullable parameters map.
|
||||
///
|
||||
/// @throws StreamsException if the stream doesn't exist
|
||||
/// @throws ConsumerRunningException if the consumer is alredy running
|
||||
/// @throws ConsumerCheckFailedException if the transformation function throws any std::exception during processing
|
||||
TransformationResult Check(const std::string &stream_name,
|
||||
std::optional<std::chrono::milliseconds> timeout = std::nullopt,
|
||||
std::optional<uint64_t> batch_limit = std::nullopt) const;
|
||||
|
||||
private:
|
||||
template <Stream TStream>
|
||||
using SynchronizedStreamSource = utils::Synchronized<TStream, utils::WritePrioritizedRWLock>;
|
||||
|
||||
template <Stream TStream>
|
||||
struct StreamData {
|
||||
std::string transformation_name;
|
||||
std::optional<std::string> owner;
|
||||
std::unique_ptr<SynchronizedStreamSource<TStream>> stream_source;
|
||||
};
|
||||
|
||||
using StreamDataVariant = std::variant<StreamData<KafkaStream>, StreamData<PulsarStream>>;
|
||||
using StreamsMap = std::unordered_map<std::string, StreamDataVariant>;
|
||||
using SynchronizedStreamsMap = utils::Synchronized<StreamsMap, utils::WritePrioritizedRWLock>;
|
||||
|
||||
template <Stream TStream>
|
||||
StreamsMap::iterator CreateConsumer(StreamsMap &map, const std::string &stream_name,
|
||||
typename TStream::StreamInfo stream_info, std::optional<std::string> owner);
|
||||
|
||||
template <Stream TStream>
|
||||
void Persist(StreamStatus<TStream> &&status) {
|
||||
const std::string stream_name = status.name;
|
||||
if (!storage_.Put(stream_name, nlohmann::json(std::move(status)).dump())) {
|
||||
throw StreamsException{"Couldn't persist steam data for stream '{}'", stream_name};
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterProcedures();
|
||||
void RegisterKafkaProcedures();
|
||||
void RegisterPulsarProcedures();
|
||||
|
||||
InterpreterContext *interpreter_context_;
|
||||
kvstore::KVStore storage_;
|
||||
|
||||
SynchronizedStreamsMap streams_;
|
||||
};
|
||||
|
||||
} // namespace stream
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -183,7 +183,7 @@ std::shared_ptr<Trigger::TriggerPlan> Trigger::GetPlan(DbAccessor *db_accessor,
|
||||
[](auto &identifier) { return &identifier.first; });
|
||||
|
||||
auto logical_plan = MakeLogicalPlan(std::move(ast_storage), utils::Downcast<CypherQuery>(parsed_statements_.query),
|
||||
parsed_statements_.parameters, nullptr, predefined_identifiers);
|
||||
parsed_statements_.parameters, db_accessor, predefined_identifiers);
|
||||
|
||||
trigger_plan_ = std::make_shared<TriggerPlan>(std::move(logical_plan), std::move(identifiers));
|
||||
}
|
||||
@@ -210,8 +210,8 @@ void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution
|
||||
ctx.symbol_table = plan.symbol_table();
|
||||
ctx.evaluation_context.timestamp = QueryTimestamp();
|
||||
ctx.evaluation_context.parameters = parsed_statements_.parameters;
|
||||
ctx.evaluation_context.properties = NamesToProperties(plan.ast_storage().properties_, nullptr);
|
||||
ctx.evaluation_context.labels = NamesToLabels(plan.ast_storage().labels_, nullptr);
|
||||
ctx.evaluation_context.properties = NamesToProperties(plan.ast_storage().properties_, dba);
|
||||
ctx.evaluation_context.labels = NamesToLabels(plan.ast_storage().labels_, dba);
|
||||
ctx.timer = utils::AsyncTimer(max_execution_time_sec);
|
||||
ctx.is_shutting_down = is_shutting_down;
|
||||
ctx.is_profile_query = false;
|
||||
|
||||
@@ -77,7 +77,7 @@ struct SetObjectProperty {
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
|
||||
return {{ObjectString<TAccessor>(), TypedValue{object}},
|
||||
{"key", TypedValue{1}}, // TODO Fix trigger
|
||||
{"key", TypedValue{dba->PropertyToName(key)}},
|
||||
{"old", old_value},
|
||||
{"new", new_value}};
|
||||
}
|
||||
@@ -97,7 +97,7 @@ struct RemovedObjectProperty {
|
||||
|
||||
std::map<std::string, TypedValue> ToMap(DbAccessor *dba) const {
|
||||
return {{ObjectString<TAccessor>(), TypedValue{object}},
|
||||
{"key", TypedValue{1}}, // TODO Fix triggers
|
||||
{"key", TypedValue{dba->PropertyToName(key)}},
|
||||
{"old", old_value}};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,8 @@ set(storage_v3_src_files
|
||||
schemas.cpp
|
||||
schema_validator.cpp
|
||||
shard.cpp
|
||||
storage.cpp
|
||||
shard_rsm.cpp
|
||||
bindings/typed_value.cpp
|
||||
expr.cpp
|
||||
request_helper.cpp
|
||||
shard_rsm.cpp
|
||||
storage.cpp)
|
||||
|
||||
# ######################
|
||||
|
||||
@@ -11,9 +11,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MG_AST_INCLUDE_PATH
|
||||
#error You are probably trying to include some files of mg-expr from both the storage and query engines! You will have a rough time kid!
|
||||
#endif
|
||||
|
||||
#define MG_AST_INCLUDE_PATH "storage/v3/bindings/ast/ast.hpp" // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#define MG_INJECTED_NAMESPACE_NAME memgraph::storage::v3 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
@@ -51,6 +50,10 @@ class DbAccessor final {
|
||||
public:
|
||||
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
std::optional<VertexAccessor> FindVertex(uint64_t /*unused*/) { return std::nullopt; }
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(primary_key, view);
|
||||
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
|
||||
@@ -78,6 +81,16 @@ class DbAccessor final {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
|
||||
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
|
||||
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
|
||||
auto maybe_vertex_acc = accessor_->CreateVertexAndValidate(primary_label, labels, properties);
|
||||
if (maybe_vertex_acc.HasError()) {
|
||||
return {std::move(maybe_vertex_acc.GetError())};
|
||||
}
|
||||
return VertexAccessor{maybe_vertex_acc.GetValue()};
|
||||
}
|
||||
|
||||
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::v3::EdgeTypeId &edge_type) {
|
||||
static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0);
|
||||
|
||||
@@ -42,7 +42,7 @@ struct Parameters {
|
||||
* @param position Token position in query of value.
|
||||
* @param value
|
||||
*/
|
||||
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace(position, value); }
|
||||
void Add(int position, const storage::v3::PropertyValue &value) { storage_.emplace_back(position, value); }
|
||||
|
||||
/**
|
||||
* Returns the value found for the given token position.
|
||||
@@ -51,11 +51,23 @@ struct Parameters {
|
||||
* @return Value for the given token position.
|
||||
*/
|
||||
const storage::v3::PropertyValue &AtTokenPosition(int position) const {
|
||||
auto found = storage_.find(position);
|
||||
auto found = std::find_if(storage_.begin(), storage_.end(), [&](const auto &a) { return a.first == position; });
|
||||
MG_ASSERT(found != storage_.end(), "Token position must be present in container");
|
||||
return found->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position-th stripped value. Asserts that this
|
||||
* container has at least (position + 1) elements.
|
||||
*
|
||||
* @param position Which stripped param is sought.
|
||||
* @return Token position and value for sought param.
|
||||
*/
|
||||
const std::pair<int, storage::v3::PropertyValue> &At(int position) const {
|
||||
MG_ASSERT(position < static_cast<int>(storage_.size()), "Invalid position");
|
||||
return storage_[position];
|
||||
}
|
||||
|
||||
/** Returns the number of arguments in this container */
|
||||
auto size() const { return storage_.size(); }
|
||||
|
||||
@@ -63,7 +75,7 @@ struct Parameters {
|
||||
auto end() const { return storage_.end(); }
|
||||
|
||||
private:
|
||||
std::unordered_map<int, storage::v3::PropertyValue> storage_;
|
||||
std::vector<std::pair<int, storage::v3::PropertyValue>> storage_;
|
||||
};
|
||||
|
||||
struct EvaluationContext {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "expr/typed_value_exception.hpp"
|
||||
#include "expr/typed_value.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
@@ -36,7 +36,6 @@ struct Edge {
|
||||
bool deleted;
|
||||
// uint8_t PAD;
|
||||
// uint16_t PAD;
|
||||
// uint32_t PAD;
|
||||
|
||||
Delta *delta;
|
||||
};
|
||||
|
||||
@@ -1,210 +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 "storage/v3/expr.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
#include "opencypher/parser.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/bindings/ast/ast.hpp"
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include "storage/v3/bindings/cypher_main_visitor.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/bindings/eval.hpp"
|
||||
#include "storage/v3/bindings/frame.hpp"
|
||||
#include "storage/v3/bindings/symbol_generator.hpp"
|
||||
#include "storage/v3/bindings/symbol_table.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/value_conversions.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
msgs::Value ConstructValueVertex(const VertexAccessor &acc, View view) {
|
||||
// Get the vertex id
|
||||
auto prim_label = acc.PrimaryLabel(view).GetValue();
|
||||
memgraph::msgs::Label value_label{.id = prim_label};
|
||||
|
||||
auto prim_key = conversions::ConvertValueVector(acc.PrimaryKey(view).GetValue());
|
||||
memgraph::msgs::VertexId vertex_id = std::make_pair(value_label, prim_key);
|
||||
|
||||
// Get the labels
|
||||
auto vertex_labels = acc.Labels(view).GetValue();
|
||||
std::vector<memgraph::msgs::Label> value_labels;
|
||||
value_labels.reserve(vertex_labels.size());
|
||||
|
||||
std::transform(vertex_labels.begin(), vertex_labels.end(), std::back_inserter(value_labels),
|
||||
[](const auto &label) { return msgs::Label{.id = label}; });
|
||||
|
||||
return msgs::Value({.id = vertex_id, .labels = value_labels});
|
||||
}
|
||||
|
||||
msgs::Value ConstructValueEdge(const EdgeAccessor &acc, View view) {
|
||||
msgs::EdgeType type = {.id = acc.EdgeType()};
|
||||
msgs::EdgeId gid = {.gid = acc.Gid().AsUint()};
|
||||
|
||||
msgs::Label src_prim_label = {.id = acc.FromVertex().primary_label};
|
||||
memgraph::msgs::VertexId src_vertex =
|
||||
std::make_pair(src_prim_label, conversions::ConvertValueVector(acc.FromVertex().primary_key));
|
||||
|
||||
msgs::Label dst_prim_label = {.id = acc.ToVertex().primary_label};
|
||||
msgs::VertexId dst_vertex =
|
||||
std::make_pair(dst_prim_label, conversions::ConvertValueVector(acc.ToVertex().primary_key));
|
||||
|
||||
auto properties = acc.Properties(view);
|
||||
|
||||
std::vector<std::pair<PropertyId, msgs::Value>> present_properties;
|
||||
if (properties.HasValue()) {
|
||||
auto props = properties.GetValue();
|
||||
std::vector<std::pair<PropertyId, msgs::Value>> present_properties;
|
||||
present_properties.reserve(props.size());
|
||||
|
||||
std::transform(props.begin(), props.end(), std::back_inserter(present_properties),
|
||||
[](std::pair<const PropertyId, PropertyValue> &prop) {
|
||||
return std::make_pair(prop.first, conversions::FromPropertyValueToValue(std::move(prop.second)));
|
||||
});
|
||||
}
|
||||
|
||||
return msgs::Value(msgs::Edge{.src = std::move(src_vertex),
|
||||
.dst = std::move(dst_vertex),
|
||||
.properties = std::move(present_properties),
|
||||
.id = gid,
|
||||
.type = type});
|
||||
}
|
||||
|
||||
msgs::Value FromTypedValueToValue(TypedValue &&tv) {
|
||||
switch (tv.type()) {
|
||||
case TypedValue::Type::Bool:
|
||||
return msgs::Value(tv.ValueBool());
|
||||
case TypedValue::Type::Double:
|
||||
return msgs::Value(tv.ValueDouble());
|
||||
case TypedValue::Type::Int:
|
||||
return msgs::Value(tv.ValueInt());
|
||||
case TypedValue::Type::List: {
|
||||
std::vector<msgs::Value> list;
|
||||
auto &tv_list = tv.ValueList();
|
||||
list.reserve(tv_list.size());
|
||||
std::transform(tv_list.begin(), tv_list.end(), std::back_inserter(list),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return msgs::Value(list);
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
std::map<std::string, msgs::Value> map;
|
||||
for (auto &[key, val] : tv.ValueMap()) {
|
||||
map.emplace(key, FromTypedValueToValue(std::move(val)));
|
||||
}
|
||||
return msgs::Value(map);
|
||||
}
|
||||
case TypedValue::Type::Null:
|
||||
return {};
|
||||
case TypedValue::Type::String:
|
||||
return msgs::Value((std::string(tv.ValueString())));
|
||||
case TypedValue::Type::Vertex:
|
||||
return ConstructValueVertex(tv.ValueVertex(), View::OLD);
|
||||
case TypedValue::Type::Edge:
|
||||
return ConstructValueEdge(tv.ValueEdge(), View::OLD);
|
||||
|
||||
// TBD -> we need to specify temporal types, not a priority.
|
||||
case TypedValue::Type::Date:
|
||||
case TypedValue::Type::LocalTime:
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
case TypedValue::Type::Duration:
|
||||
case TypedValue::Type::Path: {
|
||||
MG_ASSERT(false, "This conversion between TypedValue and Value is not implemented yet!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<msgs::Value> ConvertToValueVectorFromTypedValueVector(
|
||||
std::vector<memgraph::storage::v3::TypedValue> &&vec) {
|
||||
std::vector<msgs::Value> ret;
|
||||
ret.reserve(vec.size());
|
||||
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(ret),
|
||||
[](auto &elem) { return FromTypedValueToValue(std::move(elem)); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor &dba) {
|
||||
std::vector<PropertyId> properties;
|
||||
properties.reserve(property_names.size());
|
||||
|
||||
for (const auto &name : property_names) {
|
||||
properties.push_back(dba.NameToProperty(name));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
std::vector<memgraph::storage::v3::LabelId> NamesToLabels(const std::vector<std::string> &label_names,
|
||||
DbAccessor &dba) {
|
||||
std::vector<memgraph::storage::v3::LabelId> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &name : label_names) {
|
||||
labels.push_back(dba.NameToLabel(name));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
std::any ParseExpression(const std::string &expr, memgraph::expr::AstStorage &storage) {
|
||||
memgraph::frontend::opencypher::Parser<memgraph::frontend::opencypher::ParserOpTag::EXPRESSION> parser(expr);
|
||||
ParsingContext pc;
|
||||
CypherMainVisitor visitor(pc, &storage);
|
||||
|
||||
auto *ast = parser.tree();
|
||||
return visitor.visit(ast);
|
||||
}
|
||||
|
||||
TypedValue ComputeExpression(DbAccessor &dba, const std::optional<memgraph::storage::v3::VertexAccessor> &v_acc,
|
||||
const std::optional<memgraph::storage::v3::EdgeAccessor> &e_acc,
|
||||
const std::string &expression, std::string_view node_name, std::string_view edge_name) {
|
||||
AstStorage storage;
|
||||
Frame frame{1 + 1}; // 1 for the node_identifier, 1 for the edge_identifier
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext ctx;
|
||||
|
||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, View::OLD};
|
||||
auto expr = ParseExpression(expression, storage);
|
||||
|
||||
auto node_identifier = Identifier(std::string(node_name), false);
|
||||
auto edge_identifier = Identifier(std::string(edge_name), false);
|
||||
|
||||
std::vector<Identifier *> identifiers;
|
||||
identifiers.push_back(&node_identifier);
|
||||
identifiers.push_back(&edge_identifier);
|
||||
|
||||
expr::SymbolGenerator symbol_generator(&symbol_table, identifiers);
|
||||
(std::any_cast<Expression *>(expr))->Accept(symbol_generator);
|
||||
|
||||
if (node_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&node_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == node_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(node_identifier)] = *v_acc;
|
||||
}
|
||||
|
||||
if (edge_identifier.symbol_pos_ != -1) {
|
||||
MG_ASSERT(std::find_if(symbol_table.table().begin(), symbol_table.table().end(),
|
||||
[&edge_name](const std::pair<int32_t, Symbol> &position_symbol_pair) {
|
||||
return position_symbol_pair.second.name() == edge_name;
|
||||
}) != symbol_table.table().end());
|
||||
|
||||
frame[symbol_table.at(edge_identifier)] = *e_acc;
|
||||
}
|
||||
|
||||
return Eval(std::any_cast<Expression *>(expr), ctx, storage, eval, dba);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
@@ -1,55 +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 <vector>
|
||||
|
||||
#include "db_accessor.hpp"
|
||||
#include "opencypher/parser.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/bindings/ast/ast.hpp"
|
||||
#include "storage/v3/bindings/bindings.hpp"
|
||||
#include "storage/v3/bindings/cypher_main_visitor.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/bindings/eval.hpp"
|
||||
#include "storage/v3/bindings/frame.hpp"
|
||||
#include "storage/v3/bindings/symbol_generator.hpp"
|
||||
#include "storage/v3/bindings/symbol_table.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
memgraph::msgs::Value ConstructValueVertex(const memgraph::storage::v3::VertexAccessor &acc, View view);
|
||||
|
||||
msgs::Value ConstructValueEdge(const EdgeAccessor &acc, View view);
|
||||
|
||||
msgs::Value FromTypedValueToValue(TypedValue &&tv);
|
||||
|
||||
std::vector<msgs::Value> ConvertToValueVectorFromTypedValueVector(std::vector<TypedValue> &&vec);
|
||||
|
||||
std::vector<PropertyId> NamesToProperties(const std::vector<std::string> &property_names, DbAccessor &dba);
|
||||
|
||||
std::vector<LabelId> NamesToLabels(const std::vector<std::string> &label_names, DbAccessor &dba);
|
||||
|
||||
template <class TExpression>
|
||||
auto Eval(TExpression *expr, EvaluationContext &ctx, AstStorage &storage, ExpressionEvaluator &eval, DbAccessor &dba) {
|
||||
ctx.properties = NamesToProperties(storage.properties_, dba);
|
||||
ctx.labels = NamesToLabels(storage.labels_, dba);
|
||||
auto value = expr->Accept(eval);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::any ParseExpression(const std::string &expr, AstStorage &storage);
|
||||
|
||||
TypedValue ComputeExpression(DbAccessor &dba, const std::optional<VertexAccessor> &v_acc,
|
||||
const std::optional<EdgeAccessor> &e_acc, const std::string &expression,
|
||||
std::string_view node_name, std::string_view edge_name);
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "utils/cast.hpp"
|
||||
@@ -36,11 +35,6 @@ namespace memgraph::storage::v3 {
|
||||
constexpr uint64_t AsUint() const { return id_; } \
|
||||
constexpr int64_t AsInt() const { return std::bit_cast<int64_t>(id_); } \
|
||||
\
|
||||
friend std::ostream &operator<<(std::ostream &in, const name &n) { \
|
||||
in << n.AsInt(); \
|
||||
return in; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
uint64_t id_; \
|
||||
}; \
|
||||
|
||||
@@ -42,6 +42,7 @@ class KeyStore {
|
||||
PrimaryKey Keys() const;
|
||||
|
||||
friend bool operator<(const KeyStore &lhs, const KeyStore &rhs) {
|
||||
// TODO(antaljanosbenjamin): also compare the schema
|
||||
return std::ranges::lexicographical_compare(lhs.Keys(), rhs.Keys(), std::less<PropertyValue>{});
|
||||
}
|
||||
|
||||
@@ -50,6 +51,7 @@ class KeyStore {
|
||||
}
|
||||
|
||||
friend bool operator<(const KeyStore &lhs, const PrimaryKey &rhs) {
|
||||
// TODO(antaljanosbenjamin): also compare the schema
|
||||
return std::ranges::lexicographical_compare(lhs.Keys(), rhs, std::less<PropertyValue>{});
|
||||
}
|
||||
|
||||
|
||||
@@ -53,10 +53,6 @@ class NameIdMapper final {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto &GetIdToNameMap() const { return id_to_name_; }
|
||||
|
||||
const auto &GetNameToIdMap() const { return name_to_id_; }
|
||||
|
||||
private:
|
||||
// Necessary for comparison with string_view nad string
|
||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0919r1.html
|
||||
|
||||
@@ -1,87 +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 "storage/v3/request_helper.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "pretty_print_ast_to_original_expression.hpp"
|
||||
#include "storage/v3/bindings/db_accessor.hpp"
|
||||
#include "storage/v3/expr.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
std::vector<Element> OrderByElements(Shard::Accessor &acc, DbAccessor &dba, VerticesIterable &vertices_iterable,
|
||||
std::vector<msgs::OrderBy> &order_bys) {
|
||||
std::vector<Element> ordered;
|
||||
ordered.reserve(acc.ApproximateVertexCount());
|
||||
std::vector<Ordering> ordering;
|
||||
ordering.reserve(order_bys.size());
|
||||
for (const auto &order : order_bys) {
|
||||
switch (order.direction) {
|
||||
case memgraph::msgs::OrderingDirection::ASCENDING: {
|
||||
ordering.push_back(Ordering::ASC);
|
||||
break;
|
||||
}
|
||||
case memgraph::msgs::OrderingDirection::DESCENDING: {
|
||||
ordering.push_back(Ordering::DESC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto compare_typed_values = TypedValueVectorCompare(ordering);
|
||||
for (auto it = vertices_iterable.begin(); it != vertices_iterable.end(); ++it) {
|
||||
std::vector<TypedValue> properties_order_by;
|
||||
properties_order_by.reserve(order_bys.size());
|
||||
|
||||
for (const auto &order_by : order_bys) {
|
||||
const auto val =
|
||||
ComputeExpression(dba, *it, std::nullopt, order_by.expression.expression, expr::identifier_node_symbol, "");
|
||||
properties_order_by.push_back(val);
|
||||
}
|
||||
ordered.push_back({std::move(properties_order_by), *it});
|
||||
}
|
||||
|
||||
std::sort(ordered.begin(), ordered.end(), [compare_typed_values](const auto &pair1, const auto &pair2) {
|
||||
return compare_typed_values(pair1.properties_order_by, pair2.properties_order_by);
|
||||
});
|
||||
return ordered;
|
||||
}
|
||||
|
||||
VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable,
|
||||
const std::vector<PropertyValue> &start_ids, const View view) {
|
||||
auto it = vertex_iterable.begin();
|
||||
|
||||
if (start_ids.empty()) {
|
||||
return it;
|
||||
}
|
||||
|
||||
while (it != vertex_iterable.end()) {
|
||||
if (const auto &vertex = *it; start_ids <= vertex.PrimaryKey(view).GetValue()) {
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::vector<Element>::const_iterator GetStartOrderedElementsIterator(const std::vector<Element> &ordered_elements,
|
||||
const std::vector<PropertyValue> &start_ids,
|
||||
const View view) {
|
||||
for (auto it = ordered_elements.begin(); it != ordered_elements.end(); ++it) {
|
||||
if (const auto &vertex = it->vertex_acc; start_ids <= vertex.PrimaryKey(view).GetValue()) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return ordered_elements.end();
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
@@ -1,116 +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 <vector>
|
||||
|
||||
#include "ast/ast.hpp"
|
||||
#include "storage/v3/bindings/typed_value.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
inline bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
|
||||
// in ordering null comes after everything else
|
||||
// at the same time Null is not less that null
|
||||
// first deal with Null < Whatever case
|
||||
if (a.IsNull()) return false;
|
||||
// now deal with NotNull < Null case
|
||||
if (b.IsNull()) return true;
|
||||
|
||||
// comparisons are from this point legal only between values of
|
||||
// the same type, or int+float combinations
|
||||
if ((a.type() != b.type() && !(a.IsNumeric() && b.IsNumeric())))
|
||||
throw utils::BasicException("Can't compare value of type {} to value of type {}.", a.type(), b.type());
|
||||
|
||||
switch (a.type()) {
|
||||
case TypedValue::Type::Bool:
|
||||
return !a.ValueBool() && b.ValueBool();
|
||||
case TypedValue::Type::Int:
|
||||
if (b.type() == TypedValue::Type::Double)
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
return a.ValueInt() < b.ValueDouble();
|
||||
else
|
||||
return a.ValueInt() < b.ValueInt();
|
||||
case TypedValue::Type::Double:
|
||||
if (b.type() == TypedValue::Type::Int)
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
return a.ValueDouble() < b.ValueInt();
|
||||
else
|
||||
return a.ValueDouble() < b.ValueDouble();
|
||||
case TypedValue::Type::String:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueString() < b.ValueString();
|
||||
case TypedValue::Type::Date:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDate() < b.ValueDate();
|
||||
case TypedValue::Type::LocalTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalTime() < b.ValueLocalTime();
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalDateTime() < b.ValueLocalDateTime();
|
||||
case TypedValue::Type::Duration:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDuration() < b.ValueDuration();
|
||||
case TypedValue::Type::List:
|
||||
case TypedValue::Type::Map:
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
throw utils::BasicException("Comparison is not defined for values of type {}.", a.type());
|
||||
case TypedValue::Type::Null:
|
||||
LOG_FATAL("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
class TypedValueVectorCompare final {
|
||||
public:
|
||||
explicit TypedValueVectorCompare(const std::vector<Ordering> &ordering) : ordering_(ordering) {}
|
||||
|
||||
bool operator()(const std::vector<TypedValue> &c1, const std::vector<TypedValue> &c2) const {
|
||||
// ordering is invalid if there are more elements in the collections
|
||||
// then there are in the ordering_ vector
|
||||
MG_ASSERT(c1.size() <= ordering_.size() && c2.size() <= ordering_.size(),
|
||||
"Collections contain more elements then there are orderings");
|
||||
|
||||
auto c1_it = c1.begin();
|
||||
auto c2_it = c2.begin();
|
||||
auto ordering_it = ordering_.begin();
|
||||
for (; c1_it != c1.end() && c2_it != c2.end(); c1_it++, c2_it++, ordering_it++) {
|
||||
if (TypedValueCompare(*c1_it, *c2_it)) return *ordering_it == Ordering::ASC;
|
||||
if (TypedValueCompare(*c2_it, *c1_it)) return *ordering_it == Ordering::DESC;
|
||||
}
|
||||
|
||||
// at least one collection is exhausted
|
||||
// c1 is less then c2 iff c1 reached the end but c2 didn't
|
||||
return (c1_it == c1.end()) && (c2_it != c2.end());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Ordering> ordering_;
|
||||
};
|
||||
|
||||
struct Element {
|
||||
std::vector<TypedValue> properties_order_by;
|
||||
VertexAccessor vertex_acc;
|
||||
};
|
||||
|
||||
std::vector<Element> OrderByElements(Shard::Accessor &acc, DbAccessor &dba, VerticesIterable &vertices_iterable,
|
||||
std::vector<msgs::OrderBy> &order_bys);
|
||||
|
||||
VerticesIterable::Iterator GetStartVertexIterator(VerticesIterable &vertex_iterable,
|
||||
const std::vector<PropertyValue> &start_ids, View view);
|
||||
|
||||
std::vector<Element>::const_iterator GetStartOrderedElementsIterator(const std::vector<Element> &ordered_elements,
|
||||
const std::vector<PropertyValue> &start_ids,
|
||||
View view);
|
||||
} // namespace memgraph::storage::v3
|
||||
@@ -25,7 +25,6 @@ enum class Error : uint8_t {
|
||||
DELETED_OBJECT,
|
||||
VERTEX_HAS_EDGES,
|
||||
PROPERTIES_DISABLED,
|
||||
VERTEX_ALREADY_INSERTED
|
||||
};
|
||||
|
||||
template <class TValue>
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <cstddef>
|
||||
#include <ranges>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
@@ -40,9 +39,9 @@ SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaP
|
||||
|
||||
SchemaValidator::SchemaValidator(Schemas &schemas) : schemas_{schemas} {}
|
||||
|
||||
std::optional<SchemaViolation> SchemaValidator::ValidateVertexCreate(
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidateVertexCreate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<PropertyValue> &primary_properties) const {
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties) const {
|
||||
// Schema on primary label
|
||||
const auto *schema = schemas_.GetSchema(primary_label);
|
||||
if (schema == nullptr) {
|
||||
@@ -56,25 +55,31 @@ std::optional<SchemaViolation> SchemaValidator::ValidateVertexCreate(
|
||||
}
|
||||
}
|
||||
|
||||
// Quick size check
|
||||
if (schema->second.size() != primary_properties.size()) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PRIMARY_PROPERTIES_UNDEFINED, primary_label);
|
||||
}
|
||||
// Check only properties defined by schema
|
||||
for (size_t i{0}; i < schema->second.size(); ++i) {
|
||||
for (const auto &schema_type : schema->second) {
|
||||
// Check schema property existence
|
||||
auto property_pair = std::ranges::find_if(
|
||||
properties, [schema_property_id = schema_type.property_id](const auto &property_type_value) {
|
||||
return property_type_value.first == schema_property_id;
|
||||
});
|
||||
if (property_pair == properties.end()) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY, primary_label,
|
||||
schema_type);
|
||||
}
|
||||
|
||||
// Check schema property type
|
||||
if (auto property_schema_type = PropertyTypeToSchemaType(primary_properties[i]);
|
||||
property_schema_type && *property_schema_type != schema->second[i].type) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label,
|
||||
schema->second[i], primary_properties[i]);
|
||||
if (auto property_schema_type = PropertyTypeToSchemaType(property_pair->second);
|
||||
property_schema_type && *property_schema_type != schema_type.type) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
|
||||
property_pair->second);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<SchemaViolation> SchemaValidator::ValidatePropertyUpdate(const LabelId primary_label,
|
||||
const PropertyId property_id) const {
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidatePropertyUpdate(
|
||||
const LabelId primary_label, const PropertyId property_id) const {
|
||||
// Verify existence of schema on primary label
|
||||
const auto *schema = schemas_.GetSchema(primary_label);
|
||||
MG_ASSERT(schema, "Cannot validate against non existing schema!");
|
||||
@@ -90,7 +95,7 @@ std::optional<SchemaViolation> SchemaValidator::ValidatePropertyUpdate(const Lab
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<SchemaViolation> SchemaValidator::ValidateLabelUpdate(const LabelId label) const {
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidateLabelUpdate(const LabelId label) const {
|
||||
const auto *schema = schemas_.GetSchema(label);
|
||||
if (schema) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label);
|
||||
@@ -98,20 +103,18 @@ std::optional<SchemaViolation> SchemaValidator::ValidateLabelUpdate(const LabelI
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Schemas::Schema *SchemaValidator::GetSchema(LabelId label) const { return schemas_.GetSchema(label); }
|
||||
|
||||
VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const LabelId primary_label)
|
||||
: schema_validator{&schema_validator}, primary_label_{primary_label} {}
|
||||
|
||||
std::optional<SchemaViolation> VertexValidator::ValidatePropertyUpdate(PropertyId property_id) const {
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidatePropertyUpdate(PropertyId property_id) const {
|
||||
return schema_validator->ValidatePropertyUpdate(primary_label_, property_id);
|
||||
};
|
||||
|
||||
std::optional<SchemaViolation> VertexValidator::ValidateAddLabel(LabelId label) const {
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidateAddLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
std::optional<SchemaViolation> VertexValidator::ValidateRemoveLabel(LabelId label) const {
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexValidator::ValidateRemoveLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user