Compare commits
210 Commits
T1061-MG-i
...
E150-MG-st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15cf7794cf | ||
|
|
d42fa1fc87 | ||
|
|
2a3a3338f0 | ||
|
|
2320f95dd1 | ||
|
|
3794693356 | ||
|
|
d50b6c1abb | ||
|
|
d292162b00 | ||
|
|
9c4556618a | ||
|
|
f0dc0d911d | ||
|
|
e545beaf78 | ||
|
|
3c50b68954 | ||
|
|
fad0c893f1 | ||
|
|
866f1feeeb | ||
|
|
16d1fa2c22 | ||
|
|
518e8df04c | ||
|
|
9c453779f5 | ||
|
|
5d66a4e828 | ||
|
|
2553a8fdc3 | ||
|
|
9aeca7a4b3 | ||
|
|
1b2c8f6b29 | ||
|
|
a74a556fcc | ||
|
|
66e791d042 | ||
|
|
797c76cdfd | ||
|
|
7af917e408 | ||
|
|
c7282e8935 | ||
|
|
4ed5801588 | ||
|
|
5963c83a60 | ||
|
|
edf1293274 | ||
|
|
dd46cc407f | ||
|
|
92d69e080c | ||
|
|
629fd231b3 | ||
|
|
95e90e6c2e | ||
|
|
502a9b4823 | ||
|
|
c62b0eff93 | ||
|
|
6b9311e0b8 | ||
|
|
e3dd404865 | ||
|
|
523e2b9186 | ||
|
|
79539d13c9 | ||
|
|
6169eb221b | ||
|
|
342611691a | ||
|
|
c256dce601 | ||
|
|
2e9cf8f37d | ||
|
|
47186cab18 | ||
|
|
dcc5ec920a | ||
|
|
c7d96ed5c5 | ||
|
|
7ad9b62968 | ||
|
|
bb44aaa188 | ||
|
|
9961659103 | ||
|
|
6f906c0488 | ||
|
|
d161163fb4 | ||
|
|
1423da5f51 | ||
|
|
5e4733ac98 | ||
|
|
cbdfd54e5f | ||
|
|
69a2d73172 | ||
|
|
5b69f435b6 | ||
|
|
f098d6b331 | ||
|
|
f6edce8f0b | ||
|
|
1225230c3a | ||
|
|
5f225a623e | ||
|
|
d15105455a | ||
|
|
36c133ce4e | ||
|
|
91d48f05f3 | ||
|
|
7bac0d1c61 | ||
|
|
a4b1d5efb4 | ||
|
|
0a43afdec1 | ||
|
|
618a3d96b3 | ||
|
|
ee16954641 | ||
|
|
0aab854e34 | ||
|
|
343648f564 | ||
|
|
726fabd387 | ||
|
|
d1c5aead61 | ||
|
|
aebac2c519 | ||
|
|
74b354979c | ||
|
|
b5cff5999b | ||
|
|
b2a8063a96 | ||
|
|
54369958d1 | ||
|
|
4f06eb0f2f | ||
|
|
ad0a8c4942 | ||
|
|
997fdf5a16 | ||
|
|
8be88deee6 | ||
|
|
902a46d14f | ||
|
|
3b44ef70b6 | ||
|
|
4d8f9ea821 | ||
|
|
e935a9a7b1 | ||
|
|
dd93b594bc | ||
|
|
997d25d536 | ||
|
|
ace5f2b639 | ||
|
|
9576eea051 | ||
|
|
5bb3361a2d | ||
|
|
a84f5f6115 | ||
|
|
cacb0dac80 | ||
|
|
918fa7212e | ||
|
|
e905591372 | ||
|
|
649b5437b0 | ||
|
|
102d997288 | ||
|
|
a3f3e05fc2 | ||
|
|
4f4eb9ea13 | ||
|
|
ca638db509 | ||
|
|
7d33bb1937 | ||
|
|
b8487da392 | ||
|
|
dbd744470b | ||
|
|
b2b11f3a30 | ||
|
|
eb1b6c3ac8 | ||
|
|
0c2cbb5461 | ||
|
|
69ea79a75e | ||
|
|
c0d6cec9ab | ||
|
|
9b915be1aa | ||
|
|
b127f6f345 | ||
|
|
66ef5b2072 | ||
|
|
f877f8e1d3 | ||
|
|
0ea9878fd1 | ||
|
|
af94270dc6 | ||
|
|
8aac3ae7ea | ||
|
|
507018a630 | ||
|
|
e0bbf4766a | ||
|
|
1a48e0ffa8 | ||
|
|
6a808ce1fc | ||
|
|
a89a2d9caa | ||
|
|
c4f1764fee | ||
|
|
48a445f2ed | ||
|
|
406da4b25c | ||
|
|
8fde05444d | ||
|
|
6bdfb43ad0 | ||
|
|
9056e2c97a | ||
|
|
51371398ce | ||
|
|
b38dc28e01 | ||
|
|
6cc550719b | ||
|
|
ef70b858e2 | ||
|
|
509c12956c | ||
|
|
1527509e36 | ||
|
|
0cfb68bb89 | ||
|
|
e2968c2e21 | ||
|
|
4ee4612a9c | ||
|
|
dc38296575 | ||
|
|
06962a3ec4 | ||
|
|
c0b0b08d12 | ||
|
|
c100a86644 | ||
|
|
3ec1ff9ee4 | ||
|
|
98206caf85 | ||
|
|
962767ea1c | ||
|
|
2a199c9484 | ||
|
|
a85e9fcdd4 | ||
|
|
72b4337864 | ||
|
|
ed71332773 | ||
|
|
fd3d70d847 | ||
|
|
f8e5032011 | ||
|
|
5b59d890c0 | ||
|
|
0351db2461 | ||
|
|
c379475e12 | ||
|
|
9c5d19bc19 | ||
|
|
4d85a7e605 | ||
|
|
f6c2202772 | ||
|
|
498ae97ae9 | ||
|
|
fbd015d3c6 | ||
|
|
1ef11a36f4 | ||
|
|
9ae1671e4f | ||
|
|
b4afe45de5 | ||
|
|
581925e660 | ||
|
|
afef6dc11b | ||
|
|
689336e765 | ||
|
|
5dd0ddc352 | ||
|
|
1480d975a7 | ||
|
|
9dc37a87f7 | ||
|
|
46a2879ece | ||
|
|
b2142e8d38 | ||
|
|
0dc69c180d | ||
|
|
ad1d8637e5 | ||
|
|
4876b7cd8c | ||
|
|
a6133dab49 | ||
|
|
3466c15f76 | ||
|
|
63beeb8771 | ||
|
|
d14f7705b1 | ||
|
|
5e98971bb2 | ||
|
|
dc78adde40 | ||
|
|
80970a97f0 | ||
|
|
57533f2746 | ||
|
|
1a12a80af0 | ||
|
|
30c97e658d | ||
|
|
dd9862d32c | ||
|
|
dc6548c996 | ||
|
|
881e914b92 | ||
|
|
48ee40ce87 | ||
|
|
d6742f643c | ||
|
|
d8f09b59b3 | ||
|
|
2e5d8b7e8c | ||
|
|
88eee66258 | ||
|
|
d4cb259979 | ||
|
|
1a87dd2497 | ||
|
|
15d637729e | ||
|
|
5ee95a2e70 | ||
|
|
597a5d191c | ||
|
|
eee2a7e019 | ||
|
|
25b0a445b9 | ||
|
|
20a6dae047 | ||
|
|
04dbedc3af | ||
|
|
a0058bc10a | ||
|
|
f601e83f6f | ||
|
|
e1aab7065f | ||
|
|
28516763b9 | ||
|
|
29e8d8e72c | ||
|
|
6debc9e7d8 | ||
|
|
eb4ca543ea | ||
|
|
38ca430713 | ||
|
|
24128e0bca | ||
|
|
73719b2120 | ||
|
|
20839b0ae0 | ||
|
|
6cec9acbb9 | ||
|
|
cb70431301 | ||
|
|
0ef1f7eb5d | ||
|
|
4140f3e05e |
2
.github/workflows/diff.yaml
vendored
2
.github/workflows/diff.yaml
vendored
@@ -130,7 +130,7 @@ jobs:
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -extra-arg="-DMG_CLANG_TIDY_CHECK" -path build | tee ./build/clang_tidy_output.txt
|
||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
|
||||
|
||||
# Fail if any warning is reported
|
||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||
|
||||
2
.github/workflows/full_clang_tidy.yaml
vendored
2
.github/workflows/full_clang_tidy.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# The results are also written to standard output in order to retain them in the logs
|
||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -extra-arg="-DMG_CLANG_TIDY_CHECK" -clang-tidy-binary=/opt/toolchain-v4/bin/clang-tidy "$PWD/src/*" |
|
||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v4/bin/clang-tidy "$PWD/src/*" |
|
||||
tee ./build/full_clang_tidy_output.txt
|
||||
|
||||
- name: Summarize clang-tidy results
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -24,7 +24,6 @@ cmake/DownloadProject/
|
||||
dist/
|
||||
src/query/frontend/opencypher/generated/
|
||||
src/query/v2/frontend/opencypher/generated/
|
||||
src/parser/opencypher/generated
|
||||
tags
|
||||
ve/
|
||||
ve3/
|
||||
@@ -54,7 +53,6 @@ src/distributed/token_sharing_rpc_messages.hpp
|
||||
src/distributed/updates_rpc_messages.hpp
|
||||
src/query/v2/frontend/ast/ast.hpp
|
||||
src/query/frontend/ast/ast.hpp
|
||||
src/storage/v3/bindings/ast/ast.hpp
|
||||
src/query/distributed/frontend/ast/ast_serialization.hpp
|
||||
src/query/v2/distributed/frontend/ast/ast_serialization.hpp
|
||||
src/durability/distributed/state_delta.hpp
|
||||
@@ -62,15 +60,12 @@ src/durability/single_node/state_delta.hpp
|
||||
src/durability/single_node_ha/state_delta.hpp
|
||||
src/query/frontend/semantic/symbol.hpp
|
||||
src/query/v2/frontend/semantic/symbol.hpp
|
||||
src/expr/semantic/symbol.hpp
|
||||
src/query/distributed/frontend/semantic/symbol_serialization.hpp
|
||||
src/query/v2/distributed/frontend/semantic/symbol_serialization.hpp
|
||||
src/query/distributed/plan/ops.hpp
|
||||
src/query/v2/distributed/plan/ops.hpp
|
||||
src/query/plan/operator.hpp
|
||||
src/query/v2/plan/operator.hpp
|
||||
src/parser/opencypher/generated
|
||||
src/expr/semantic/symbol.hpp
|
||||
src/raft/log_entry.hpp
|
||||
src/raft/raft_rpc_messages.hpp
|
||||
src/raft/snapshot_metadata.hpp
|
||||
|
||||
@@ -18,8 +18,6 @@ add_subdirectory(query/v2)
|
||||
add_subdirectory(slk)
|
||||
add_subdirectory(rpc)
|
||||
add_subdirectory(auth)
|
||||
add_subdirectory(parser)
|
||||
add_subdirectory(expr)
|
||||
add_subdirectory(coordinator)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
set(coordinator_src_files
|
||||
coordinator.cpp
|
||||
shard_map.cpp)
|
||||
coordinator.hpp
|
||||
shard_map.hpp
|
||||
hybrid_logical_clock.hpp)
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(mg-coordinator STATIC ${coordinator_src_files})
|
||||
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils mg-storage-v3)
|
||||
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils)
|
||||
|
||||
@@ -1,129 +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 <coordinator/coordinator.hpp>
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(HeartbeatRequest &&heartbeat_request) {
|
||||
spdlog::info("Coordinator handling HeartbeatRequest");
|
||||
|
||||
// add this storage engine to any under-replicated shards that it is not already a part of
|
||||
|
||||
auto initializing_rsms_for_shard_manager =
|
||||
shard_map_.AssignShards(heartbeat_request.from_storage_manager, heartbeat_request.initialized_rsms);
|
||||
|
||||
return HeartbeatResponse{
|
||||
.shards_to_initialize = initializing_rsms_for_shard_manager,
|
||||
};
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(HlcRequest &&hlc_request) {
|
||||
HlcResponse res{};
|
||||
|
||||
auto hlc_shard_map = shard_map_.GetHlc();
|
||||
|
||||
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
|
||||
|
||||
res.new_hlc = Hlc{
|
||||
.logical_id = ++highest_allocated_timestamp_,
|
||||
// TODO(tyler) probably pass some more context to the Coordinator here
|
||||
// so that we can use our wall clock and enforce monotonicity.
|
||||
// .coordinator_wall_clock = io_.Now(),
|
||||
};
|
||||
|
||||
// Allways return fresher shard_map for now.
|
||||
res.fresher_shard_map = std::make_optional(shard_map_);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
|
||||
AllocateEdgeIdBatchResponse res{};
|
||||
|
||||
uint64_t low = highest_allocated_edge_id_;
|
||||
|
||||
highest_allocated_edge_id_ += ahr.batch_size;
|
||||
|
||||
uint64_t high = highest_allocated_edge_id_;
|
||||
|
||||
res.low = low;
|
||||
res.high = high;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This splits the shard immediately beneath the provided
|
||||
/// split key, keeping the assigned peers identical for now,
|
||||
/// but letting them be gradually migrated over time.
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(SplitShardRequest &&split_shard_request) {
|
||||
SplitShardResponse res{};
|
||||
|
||||
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
|
||||
res.success = false;
|
||||
} else {
|
||||
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
|
||||
split_shard_request.split_key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This adds the provided storage engine to the standby storage engine pool,
|
||||
/// which can be used to rebalance storage over time.
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(
|
||||
RegisterStorageEngineRequest && /* register_storage_engine_request */) {
|
||||
RegisterStorageEngineResponse res{};
|
||||
// TODO
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This begins the process of draining the provided storage engine from all raft
|
||||
/// clusters that it might be participating in.
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(
|
||||
DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
|
||||
DeregisterStorageEngineResponse res{};
|
||||
// TODO
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
|
||||
InitializeLabelResponse res{};
|
||||
|
||||
std::optional<LabelId> new_label_id = shard_map_.InitializeNewLabel(
|
||||
initialize_label_request.label_name, initialize_label_request.schema, initialize_label_request.replication_factor,
|
||||
initialize_label_request.last_shard_map_version);
|
||||
|
||||
if (new_label_id) {
|
||||
res.new_label_id = new_label_id.value();
|
||||
res.fresher_shard_map = std::nullopt;
|
||||
res.success = true;
|
||||
} else {
|
||||
res.fresher_shard_map = shard_map_;
|
||||
res.success = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
|
||||
AllocatePropertyIdsResponse res{};
|
||||
|
||||
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
|
||||
|
||||
res.property_ids = property_ids;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
@@ -11,31 +11,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
|
||||
#include <coordinator/hybrid_logical_clock.hpp>
|
||||
#include <coordinator/shard_map.hpp>
|
||||
#include <io/simulator/simulator.hpp>
|
||||
#include <io/time.hpp>
|
||||
#include <io/transport.hpp>
|
||||
#include <storage/v3/id_types.hpp>
|
||||
#include <storage/v3/schemas.hpp>
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "coordinator/shard_map.hpp"
|
||||
#include "io/simulator/simulator.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
using memgraph::io::Address;
|
||||
using memgraph::storage::v3::LabelId;
|
||||
using memgraph::storage::v3::PropertyId;
|
||||
using memgraph::storage::v3::SchemaProperty;
|
||||
using Address = memgraph::io::Address;
|
||||
using SimT = memgraph::io::simulator::SimulatorTransport;
|
||||
using PrimaryKey = std::vector<PropertyValue>;
|
||||
|
||||
struct HlcRequest {
|
||||
Hlc last_shard_map_version;
|
||||
@@ -74,18 +61,10 @@ struct AllocateEdgeIdBatchResponse {
|
||||
uint64_t high;
|
||||
};
|
||||
|
||||
struct AllocatePropertyIdsRequest {
|
||||
std::vector<std::string> property_names;
|
||||
};
|
||||
|
||||
struct AllocatePropertyIdsResponse {
|
||||
std::map<std::string, PropertyId> property_ids;
|
||||
};
|
||||
|
||||
struct SplitShardRequest {
|
||||
Hlc previous_shard_map_version;
|
||||
LabelId label_id;
|
||||
PrimaryKey split_key;
|
||||
Label label;
|
||||
CompoundKey split_key;
|
||||
};
|
||||
|
||||
struct SplitShardResponse {
|
||||
@@ -110,87 +89,155 @@ struct DeregisterStorageEngineResponse {
|
||||
|
||||
struct InitializeLabelRequest {
|
||||
std::string label_name;
|
||||
std::vector<SchemaProperty> schema;
|
||||
size_t replication_factor;
|
||||
Hlc last_shard_map_version;
|
||||
};
|
||||
|
||||
struct InitializeLabelResponse {
|
||||
bool success;
|
||||
LabelId new_label_id;
|
||||
std::optional<ShardMap> fresher_shard_map;
|
||||
};
|
||||
|
||||
struct HeartbeatRequest {
|
||||
Address from_storage_manager;
|
||||
std::set<boost::uuids::uuid> initialized_rsms;
|
||||
};
|
||||
using WriteRequests =
|
||||
std::variant<AllocateHlcBatchRequest, AllocateEdgeIdBatchRequest, SplitShardRequest, RegisterStorageEngineRequest,
|
||||
DeregisterStorageEngineRequest, InitializeLabelRequest>;
|
||||
using WriteResponses =
|
||||
std::variant<AllocateHlcBatchResponse, AllocateEdgeIdBatchResponse, SplitShardResponse,
|
||||
RegisterStorageEngineResponse, DeregisterStorageEngineResponse, InitializeLabelResponse>;
|
||||
|
||||
struct HeartbeatResponse {
|
||||
std::vector<ShardToInitialize> shards_to_initialize;
|
||||
};
|
||||
|
||||
using CoordinatorWriteRequests =
|
||||
std::variant<HlcRequest, AllocateEdgeIdBatchRequest, SplitShardRequest, RegisterStorageEngineRequest,
|
||||
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest, HeartbeatRequest>;
|
||||
using CoordinatorWriteResponses = std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse,
|
||||
RegisterStorageEngineResponse, DeregisterStorageEngineResponse,
|
||||
InitializeLabelResponse, AllocatePropertyIdsResponse, HeartbeatResponse>;
|
||||
|
||||
using CoordinatorReadRequests = std::variant<GetShardMapRequest>;
|
||||
using CoordinatorReadResponses = std::variant<GetShardMapResponse>;
|
||||
using ReadRequests = std::variant<HlcRequest, GetShardMapRequest>;
|
||||
using ReadResponses = std::variant<HlcResponse, GetShardMapResponse>;
|
||||
|
||||
class Coordinator {
|
||||
public:
|
||||
explicit Coordinator(ShardMap sm) : shard_map_{std::move(sm)} {}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static
|
||||
CoordinatorReadResponses Read(CoordinatorReadRequests requests) {
|
||||
return std::visit([&](auto &&request) { return HandleRead(std::forward<decltype(request)>(request)); },
|
||||
std::move(requests)); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static
|
||||
CoordinatorWriteResponses Apply(CoordinatorWriteRequests requests) {
|
||||
return std::visit([&](auto &&request) mutable { return ApplyWrite(std::forward<decltype(request)>(request)); },
|
||||
std::move(requests));
|
||||
}
|
||||
|
||||
private:
|
||||
ShardMap shard_map_;
|
||||
/// The highest reserved timestamp / highest allocated timestamp
|
||||
/// is a way for minimizing communication involved in query engines
|
||||
/// reserving Hlc's for their transaction processing.
|
||||
/// Periodically, the coordinator will allocate a batch of timestamps
|
||||
/// and this will need to go over consensus. From that point forward,
|
||||
/// each timestamp in that batch can be given out to "readers" who issue
|
||||
/// HlcRequest without blocking on consensus first. But if
|
||||
/// highest_allocated_timestamp_ approaches highest_reserved_timestamp_,
|
||||
/// it is time to allocate another batch, so that we can keep guaranteeing
|
||||
/// forward progress.
|
||||
/// Any time a coordinator becomes a new leader, it will need to issue
|
||||
/// a new AllocateHlcBatchRequest to create a pool of IDs to allocate.
|
||||
uint64_t highest_allocated_timestamp_;
|
||||
uint64_t highest_reserved_timestamp_;
|
||||
|
||||
/// Query engines need to periodically request batches of unique edge IDs.
|
||||
uint64_t highest_allocated_edge_id_;
|
||||
|
||||
CoordinatorReadResponses HandleRead(GetShardMapRequest && /* get_shard_map_request */) {
|
||||
/// Increment our
|
||||
ReadResponses HandleRead(HlcRequest &&hlc_request) {
|
||||
HlcResponse res{};
|
||||
|
||||
auto hlc_shard_map = shard_map_.GetHlc();
|
||||
|
||||
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
|
||||
|
||||
res.new_hlc = shard_map_.IncrementShardMapVersion();
|
||||
|
||||
// res.fresher_shard_map = hlc_request.last_shard_map_version.logical_id < hlc_shard_map.logical_id
|
||||
// ? std::make_optional(shard_map_)
|
||||
// : std::nullopt;
|
||||
|
||||
// Allways return fresher shard_map for now.
|
||||
res.fresher_shard_map = std::make_optional(shard_map_);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ReadResponses HandleRead(GetShardMapRequest &&get_shard_map_request) {
|
||||
GetShardMapResponse res;
|
||||
res.shard_map = shard_map_;
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses ApplyWrite(HeartbeatRequest &&heartbeat_request);
|
||||
WriteResponses ApplyWrite(AllocateHlcBatchRequest &&ahr) {
|
||||
AllocateHlcBatchResponse res{};
|
||||
|
||||
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request);
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr);
|
||||
WriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
|
||||
AllocateEdgeIdBatchResponse res{};
|
||||
|
||||
uint64_t low = highest_allocated_edge_id_;
|
||||
|
||||
highest_allocated_edge_id_ += ahr.batch_size;
|
||||
|
||||
uint64_t high = highest_allocated_edge_id_;
|
||||
|
||||
res.low = low;
|
||||
res.high = high;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This splits the shard immediately beneath the provided
|
||||
/// split key, keeping the assigned peers identical for now,
|
||||
/// but letting them be gradually migrated over time.
|
||||
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request);
|
||||
WriteResponses ApplyWrite(SplitShardRequest &&split_shard_request) {
|
||||
SplitShardResponse res{};
|
||||
|
||||
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
|
||||
res.success = false;
|
||||
} else {
|
||||
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label,
|
||||
split_shard_request.split_key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This adds the provided storage engine to the standby storage engine pool,
|
||||
/// which can be used to rebalance storage over time.
|
||||
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */);
|
||||
WriteResponses ApplyWrite(RegisterStorageEngineRequest &®ister_storage_engine_request) {
|
||||
RegisterStorageEngineResponse res{};
|
||||
// TODO
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// This begins the process of draining the provided storage engine from all raft
|
||||
/// clusters that it might be participating in.
|
||||
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */);
|
||||
WriteResponses ApplyWrite(DeregisterStorageEngineRequest &®ister_storage_engine_request) {
|
||||
DeregisterStorageEngineResponse res{};
|
||||
// TODO
|
||||
// const Address &address = register_storage_engine_request.address;
|
||||
// storage_engine_pool_.erase(address);
|
||||
// res.success = true;
|
||||
|
||||
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request);
|
||||
return res;
|
||||
}
|
||||
|
||||
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request);
|
||||
WriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
|
||||
InitializeLabelResponse res{};
|
||||
|
||||
bool success = shard_map_.InitializeNewLabel(initialize_label_request.label_name,
|
||||
initialize_label_request.last_shard_map_version);
|
||||
|
||||
if (success) {
|
||||
res.fresher_shard_map = shard_map_;
|
||||
res.success = false;
|
||||
} else {
|
||||
res.fresher_shard_map = std::nullopt;
|
||||
res.success = true;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit Coordinator(ShardMap sm) : shard_map_{(sm)} {}
|
||||
|
||||
ReadResponses Read(ReadRequests requests) {
|
||||
return std::visit([&](auto &&requests) { return HandleRead(std::move(requests)); }, std::move(requests));
|
||||
}
|
||||
|
||||
WriteResponses Apply(WriteRequests requests) {
|
||||
return std::visit([&](auto &&requests) { return ApplyWrite(std::move(requests)); }, std::move(requests));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "coordinator/coordinator.hpp"
|
||||
#include "io/rsm/rsm_client.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
using memgraph::io::rsm::RsmClient;
|
||||
|
||||
template <typename IoImpl>
|
||||
using CoordinatorClient = RsmClient<IoImpl, CoordinatorWriteRequests, CoordinatorWriteResponses,
|
||||
CoordinatorReadRequests, CoordinatorReadResponses>;
|
||||
} // namespace memgraph::coordinator
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
|
||||
#include "io/time.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
@@ -24,13 +22,9 @@ struct Hlc {
|
||||
uint64_t logical_id;
|
||||
Time coordinator_wall_clock;
|
||||
|
||||
auto operator<=>(const Hlc &other) const { return logical_id <=> other.logical_id; }
|
||||
|
||||
bool operator==(const Hlc &other) const = default;
|
||||
bool operator<(const Hlc &other) const = default;
|
||||
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; }
|
||||
bool operator==(const Hlc &other) const {
|
||||
return (logical_id == other.logical_id) && (coordinator_wall_clock == other.coordinator_wall_clock);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -1,90 +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 "coordinator/shard_map.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
using memgraph::common::SchemaType;
|
||||
using memgraph::storage::v3::TemporalData;
|
||||
using memgraph::storage::v3::TemporalType;
|
||||
|
||||
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema) {
|
||||
PrimaryKey ret{};
|
||||
|
||||
const int64_t min_int = std::numeric_limits<int64_t>::min();
|
||||
|
||||
const TemporalData date{TemporalType::Date, min_int};
|
||||
const TemporalData local_time{TemporalType::LocalTime, min_int};
|
||||
const TemporalData local_date_time{TemporalType::LocalDateTime, min_int};
|
||||
const TemporalData duration{TemporalType::Duration, min_int};
|
||||
|
||||
for (const auto &schema_property : schema) {
|
||||
switch (schema_property.type) {
|
||||
case SchemaType::BOOL:
|
||||
ret.emplace_back(PropertyValue(false));
|
||||
break;
|
||||
case SchemaType::INT:
|
||||
ret.emplace_back(PropertyValue(min_int));
|
||||
break;
|
||||
case SchemaType::STRING:
|
||||
ret.emplace_back(PropertyValue(""));
|
||||
break;
|
||||
case SchemaType::DATE:
|
||||
ret.emplace_back(PropertyValue(date));
|
||||
break;
|
||||
case SchemaType::LOCALTIME:
|
||||
ret.emplace_back(PropertyValue(local_time));
|
||||
break;
|
||||
case SchemaType::LOCALDATETIME:
|
||||
ret.emplace_back(PropertyValue(local_date_time));
|
||||
break;
|
||||
case SchemaType::DURATION:
|
||||
ret.emplace_back(PropertyValue(duration));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const LabelId label_id = LabelId::FromUint(++max_label_id);
|
||||
|
||||
labels.emplace(std::move(label_name), label_id);
|
||||
|
||||
PrimaryKey initial_key = SchemaToMinKey(schema);
|
||||
Shard empty_shard = {};
|
||||
|
||||
Shards shards = {
|
||||
{initial_key, empty_shard},
|
||||
};
|
||||
|
||||
LabelSpace label_space{
|
||||
.schema = std::move(schema),
|
||||
.shards = shards,
|
||||
.replication_factor = replication_factor,
|
||||
};
|
||||
|
||||
label_spaces.emplace(label_id, label_space);
|
||||
|
||||
IncrementShardMapVersion();
|
||||
|
||||
return label_id;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
@@ -11,32 +11,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "io/address.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
|
||||
using memgraph::io::Address;
|
||||
using memgraph::storage::v3::Config;
|
||||
using memgraph::storage::v3::LabelId;
|
||||
using memgraph::storage::v3::PropertyId;
|
||||
using memgraph::storage::v3::PropertyValue;
|
||||
using memgraph::storage::v3::SchemaProperty;
|
||||
|
||||
enum class Status : uint8_t {
|
||||
CONSENSUS_PARTICIPANT,
|
||||
INITIALIZING,
|
||||
@@ -48,46 +31,20 @@ 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; }
|
||||
};
|
||||
|
||||
using PrimaryKey = std::vector<PropertyValue>;
|
||||
using memgraph::io::Address;
|
||||
|
||||
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
|
||||
using Shard = std::vector<AddressAndStatus>;
|
||||
using Shards = std::map<PrimaryKey, Shard>;
|
||||
using LabelName = std::string;
|
||||
using PropertyName = std::string;
|
||||
using PropertyMap = std::map<PropertyName, PropertyId>;
|
||||
using Shards = std::map<CompoundKey, Shard>;
|
||||
|
||||
struct ShardToInitialize {
|
||||
boost::uuids::uuid uuid;
|
||||
LabelId label_id;
|
||||
PrimaryKey min_key;
|
||||
std::optional<PrimaryKey> max_key;
|
||||
Config config;
|
||||
};
|
||||
|
||||
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
|
||||
|
||||
struct LabelSpace {
|
||||
std::vector<SchemaProperty> schema;
|
||||
std::map<PrimaryKey, Shard> shards;
|
||||
size_t replication_factor;
|
||||
};
|
||||
// use string for intermachine communication and NameIdMapper within the machine
|
||||
using Label = std::string;
|
||||
|
||||
struct ShardMap {
|
||||
Hlc shard_map_version;
|
||||
uint64_t max_property_id;
|
||||
std::map<PropertyName, PropertyId> properties;
|
||||
uint64_t max_label_id;
|
||||
std::map<LabelName, LabelId> labels;
|
||||
std::map<LabelId, LabelSpace> label_spaces;
|
||||
std::map<LabelId, std::vector<SchemaProperty>> schemas;
|
||||
|
||||
Shards GetShards(const LabelName &label) {
|
||||
const auto id = labels.at(label);
|
||||
auto &shards = label_spaces.at(id).shards;
|
||||
return shards;
|
||||
}
|
||||
std::map<Label, Shards> shards;
|
||||
|
||||
// TODO(gabor) later we will want to update the wallclock time with
|
||||
// the given Io<impl>'s time as well
|
||||
@@ -98,179 +55,81 @@ struct ShardMap {
|
||||
|
||||
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> ret{};
|
||||
bool SplitShard(Hlc previous_shard_map_version, Label label, CompoundKey key) {
|
||||
if (CompareShardMapVersions(previous_shard_map_version, shard_map_version)) {
|
||||
MG_ASSERT(shards.contains(label));
|
||||
auto &shards_in_map = shards[label];
|
||||
MG_ASSERT(!shards_in_map.contains(key));
|
||||
|
||||
bool mutated = false;
|
||||
// Finding the Shard that the new CompoundKey should map to.
|
||||
Shard shard_to_map_to;
|
||||
CompoundKey prev_key = ((*shards_in_map.begin()).first);
|
||||
|
||||
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{},
|
||||
});
|
||||
}
|
||||
}
|
||||
for (auto iter = std::next(shards_in_map.begin()); iter != shards_in_map.end(); ++iter) {
|
||||
const auto ¤t_key = (*iter).first;
|
||||
if (key > prev_key && key < current_key) {
|
||||
shard_to_map_to = shards_in_map[prev_key];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
prev_key = (*iter).first;
|
||||
}
|
||||
|
||||
// Apply the split
|
||||
shards_in_map[key] = shard_to_map_to;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
}
|
||||
|
||||
return ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
|
||||
if (previous_shard_map_version != shard_map_version) {
|
||||
bool InitializeNewLabel(std::string label_name, Hlc last_shard_map_version) {
|
||||
if (shard_map_version != last_shard_map_version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &label_space = label_spaces.at(label_id);
|
||||
auto &shards_in_map = label_space.shards;
|
||||
if (shards.contains(label_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MG_ASSERT(!shards_in_map.empty());
|
||||
MG_ASSERT(!shards_in_map.contains(key));
|
||||
MG_ASSERT(label_spaces.contains(label_id));
|
||||
shards.emplace(label_name, Shards{});
|
||||
|
||||
// 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> InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
|
||||
size_t replication_factor, Hlc last_shard_map_version);
|
||||
|
||||
void AddServer(Address server_address) {
|
||||
// Find a random place for the server to plug in
|
||||
}
|
||||
|
||||
LabelId GetLabelId(const std::string &label) const { return labels.at(label); }
|
||||
std::map<Label, Shards> &GetShards() noexcept { return shards; }
|
||||
|
||||
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));
|
||||
Shards GetShardsForRange(Label label, CompoundKey start, CompoundKey end);
|
||||
|
||||
LabelId label_id = labels.at(label_name);
|
||||
Shard GetShardForKey(Label label, CompoundKey key) {
|
||||
auto shard_for_label = shards.at(label);
|
||||
|
||||
const auto &label_space = label_spaces.at(label_id);
|
||||
auto max = (--shard_for_label.end())->first;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (key > max) {
|
||||
return shard_for_label[max];
|
||||
}
|
||||
|
||||
if (mutated) {
|
||||
IncrementShardMapVersion();
|
||||
for (auto it = shard_for_label.lower_bound(key);; --it) {
|
||||
MG_ASSERT(it->first <= key);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return ret;
|
||||
MG_ASSERT(false, "failed to find shard with a key that is less than or equal to the provided key");
|
||||
}
|
||||
|
||||
std::optional<PropertyId> GetPropertyId(const std::string &property_name) const {
|
||||
if (properties.contains(property_name)) {
|
||||
return properties.at(property_name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
private:
|
||||
// TODO(gabor) later we will want to update the wallclock time with
|
||||
// the given Io<impl>'s time as well. This function should just be
|
||||
// replaced with operator== since it is already overloaded for Hlc
|
||||
// objects.
|
||||
bool CompareShardMapVersions(Hlc one, Hlc two) { return one.logical_id == two.logical_id; }
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
define_add_lcp(add_lcp_expr lcp_expr_cpp_files generated_lcp_expr_files)
|
||||
|
||||
add_lcp_expr(semantic/symbol.lcp)
|
||||
|
||||
add_custom_target(generate_lcp_expr DEPENDS ${generated_lcp_expr_files})
|
||||
|
||||
set(mg_expr_sources
|
||||
${lcp_expr_cpp_files}
|
||||
parsing.cpp)
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_library(mg-expr STATIC ${mg_expr_sources})
|
||||
add_dependencies(mg-expr generate_lcp_expr)
|
||||
target_include_directories(mg-expr PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/ast)
|
||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interpret)
|
||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/semantic)
|
||||
target_link_libraries(mg-expr cppitertools Boost::headers mg-utils mg-parser)
|
||||
@@ -1,35 +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
|
||||
|
||||
#ifndef MG_AST_INCLUDE_PATH
|
||||
#ifdef MG_CLANG_TIDY_CHECK
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define MG_AST_INCLUDE_PATH "query/v2/frontend/ast/ast.hpp"
|
||||
#else
|
||||
#error Missing AST include path
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#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
|
||||
#endif
|
||||
|
||||
#include MG_AST_INCLUDE_PATH
|
||||
|
||||
namespace memgraph::expr {
|
||||
using namespace MG_INJECTED_NAMESPACE_NAME; // NOLINT(google-build-using-namespace)
|
||||
} // namespace memgraph::expr
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,271 +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 {
|
||||
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 << utils::Escape(str); }
|
||||
|
||||
inline void PrintObject(std::ostream *out, Aggregation::Op op) { *out << Aggregation::OpToString(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) {
|
||||
*out << "[";
|
||||
utils::PrintIterable(*out, vec, ", ", [](auto &stream, const auto &item) { PrintObject(&stream, item); });
|
||||
*out << "]";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const std::vector<T, utils::Allocator<T>> &vec) {
|
||||
*out << "[";
|
||||
utils::PrintIterable(*out, vec, ", ", [](auto &stream, const auto &item) { PrintObject(&stream, item); });
|
||||
*out << "]";
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void PrintObject(std::ostream *out, const std::map<K, V> &map) {
|
||||
*out << "{";
|
||||
utils::PrintIterable(*out, map, ", ", [](auto &stream, const auto &item) {
|
||||
PrintObject(&stream, item.first);
|
||||
stream << ": ";
|
||||
PrintObject(&stream, item.second);
|
||||
});
|
||||
*out << "}";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const utils::pmr::map<utils::pmr::string, T> &map) {
|
||||
*out << "{";
|
||||
utils::PrintIterable(*out, map, ", ", [](auto &stream, const auto &item) {
|
||||
PrintObject(&stream, item.first);
|
||||
stream << ": ";
|
||||
PrintObject(&stream, item.second);
|
||||
});
|
||||
*out << "}";
|
||||
}
|
||||
|
||||
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(std::ostream *out, const T &arg) {
|
||||
*out << " ";
|
||||
PrintObject(out, arg);
|
||||
*out << ")";
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void PrintOperatorArgs(std::ostream *out, const T &arg, const Ts &...args) {
|
||||
*out << " ";
|
||||
PrintObject(out, arg);
|
||||
PrintOperatorArgs(out, args...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void PrintOperator(std::ostream *out, const std::string &name, const Ts &...args) {
|
||||
*out << "(" << name;
|
||||
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(out_, OP_STR, 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(out_, OP_STR, op.expression1_, op.expression2_); }
|
||||
|
||||
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(InListOperator, "In");
|
||||
BINARY_OPERATOR_VISIT(SubscriptOperator, "Subscript");
|
||||
|
||||
#undef BINARY_OPERATOR_VISIT
|
||||
|
||||
// Other
|
||||
void Visit(ListSlicingOperator &op) override {
|
||||
detail::PrintOperator(out_, "ListSlicing", op.list_, op.lower_bound_, op.upper_bound_);
|
||||
}
|
||||
|
||||
void Visit(IfOperator &op) override {
|
||||
detail::PrintOperator(out_, "If", op.condition_, op.then_expression_, op.else_expression_);
|
||||
}
|
||||
|
||||
void Visit(ListLiteral &op) override { detail::PrintOperator(out_, "ListLiteral", op.elements_); }
|
||||
|
||||
void Visit(MapLiteral &op) override {
|
||||
std::map<std::string, Expression *> map;
|
||||
for (const auto &kv : op.elements_) {
|
||||
map[kv.first.name] = kv.second;
|
||||
}
|
||||
detail::PrintObject(out_, map);
|
||||
}
|
||||
|
||||
void Visit(LabelsTest &op) override { detail::PrintOperator(out_, "LabelsTest", op.expression_); }
|
||||
|
||||
void Visit(Aggregation &op) override { detail::PrintOperator(out_, "Aggregation", op.op_); }
|
||||
|
||||
void Visit(Function &op) override { detail::PrintOperator(out_, "Function", op.function_name_, op.arguments_); }
|
||||
|
||||
void Visit(Reduce &op) override {
|
||||
detail::PrintOperator(out_, "Reduce", op.accumulator_, op.initializer_, op.identifier_, op.list_, op.expression_);
|
||||
}
|
||||
|
||||
void Visit(Coalesce &op) override { detail::PrintOperator(out_, "Coalesce", op.expressions_); }
|
||||
|
||||
void Visit(Extract &op) override { detail::PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_); }
|
||||
|
||||
void Visit(All &op) override {
|
||||
detail::PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void Visit(Single &op) override {
|
||||
detail::PrintOperator(out_, "Single", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void Visit(Any &op) override {
|
||||
detail::PrintOperator(out_, "Any", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void Visit(None &op) override {
|
||||
detail::PrintOperator(out_, "None", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void Visit(Identifier &op) override { detail::PrintOperator(out_, "Identifier", op.name_); }
|
||||
|
||||
void Visit(PrimitiveLiteral &op) override { detail::PrintObject(out_, op.value_); }
|
||||
|
||||
void Visit(PropertyLookup &op) override {
|
||||
detail::PrintOperator(out_, "PropertyLookup", op.expression_, op.property_.name);
|
||||
}
|
||||
|
||||
void Visit(ParameterLookup &op) override { detail::PrintOperator(out_, "ParameterLookup", op.token_position_); }
|
||||
|
||||
void Visit(NamedExpression &op) override { detail::PrintOperator(out_, "NamedExpression", op.name_, op.expression_); }
|
||||
|
||||
void Visit(RegexMatch &op) override { detail::PrintOperator(out_, "=~", op.string_expr_, op.regex_); }
|
||||
|
||||
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 PrintExpression(Expression *expr, std::ostream *out) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
}
|
||||
|
||||
inline void PrintExpression(NamedExpression *expr, std::ostream *out) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
}
|
||||
} // namespace memgraph::expr
|
||||
@@ -1,52 +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 {
|
||||
|
||||
class SyntaxException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
SyntaxException() : SyntaxException("") {}
|
||||
};
|
||||
|
||||
class SemanticException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
SemanticException() : BasicException("") {}
|
||||
};
|
||||
|
||||
class ExpressionRuntimeException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
class RedeclareVariableError : public SemanticException {
|
||||
public:
|
||||
explicit RedeclareVariableError(const std::string &name) : SemanticException("Redeclaring variable: " + name + ".") {}
|
||||
};
|
||||
|
||||
class UnboundVariableError : public SemanticException {
|
||||
public:
|
||||
explicit UnboundVariableError(const std::string &name) : SemanticException("Unbound variable: " + name + ".") {}
|
||||
};
|
||||
|
||||
class TypeMismatchError : public SemanticException {
|
||||
public:
|
||||
TypeMismatchError(const std::string &name, const std::string &datum, const std::string &expected)
|
||||
: SemanticException(fmt::format("Type mismatch: {} already defined as {}, expected {}.", name, datum, expected)) {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::expr
|
||||
@@ -1,712 +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.
|
||||
|
||||
// Copyright 2017 Memgraph
|
||||
//
|
||||
// Created by Teon Banek on 11-03-2017
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "expr/ast.hpp"
|
||||
#include "expr/ast/ast_visitor.hpp"
|
||||
#include "expr/exceptions.hpp"
|
||||
#include "expr/semantic/symbol_table.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace detail {
|
||||
inline std::unordered_map<std::string, Identifier *> GeneratePredefinedIdentifierMap(
|
||||
const std::vector<Identifier *> &predefined_identifiers) {
|
||||
std::unordered_map<std::string, Identifier *> identifier_map;
|
||||
for (const auto &identifier : predefined_identifiers) {
|
||||
identifier_map.emplace(identifier->name_, identifier);
|
||||
}
|
||||
|
||||
return identifier_map;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/// Visits the AST and generates symbols for variables.
|
||||
///
|
||||
/// During the process of symbol generation, simple semantic checks are
|
||||
/// performed. Such as, redeclaring a variable or conflicting expectations of
|
||||
/// variable types.
|
||||
class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
public:
|
||||
explicit SymbolGenerator(SymbolTable *symbol_table, const std::vector<Identifier *> &predefined_identifiers)
|
||||
: symbol_table_(symbol_table),
|
||||
predefined_identifiers_{detail::GeneratePredefinedIdentifierMap(predefined_identifiers)},
|
||||
scopes_(1, Scope()) {}
|
||||
|
||||
using HierarchicalTreeVisitor::PostVisit;
|
||||
using HierarchicalTreeVisitor::PreVisit;
|
||||
using HierarchicalTreeVisitor::Visit;
|
||||
using typename HierarchicalTreeVisitor::ReturnType;
|
||||
|
||||
// Query
|
||||
bool PreVisit(SingleQuery & /*unused*/) override {
|
||||
prev_return_names_ = curr_return_names_;
|
||||
curr_return_names_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Union
|
||||
bool PreVisit(CypherUnion & /*unused*/) override {
|
||||
scopes_.back() = Scope();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(CypherUnion &cypher_union) override {
|
||||
if (prev_return_names_ != curr_return_names_) {
|
||||
throw SemanticException("All subqueries in an UNION must have the same column names.");
|
||||
}
|
||||
|
||||
// create new symbols for the result of the union
|
||||
for (const auto &name : curr_return_names_) {
|
||||
auto symbol = CreateSymbol(name, false);
|
||||
cypher_union.union_symbols_.push_back(symbol);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clauses
|
||||
bool PreVisit(Create & /*unused*/) override {
|
||||
scopes_.back().in_create = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Create & /*unused*/) override {
|
||||
scopes_.back().in_create = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(CallProcedure &call_proc) override {
|
||||
for (auto *expr : call_proc.arguments_) {
|
||||
expr->Accept(*this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PostVisit(CallProcedure &call_proc) override {
|
||||
for (auto *ident : call_proc.result_identifiers_) {
|
||||
if (HasSymbolLocalScope(ident->name_)) {
|
||||
throw RedeclareVariableError(ident->name_);
|
||||
}
|
||||
ident->MapTo(CreateSymbol(ident->name_, true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(LoadCsv & /*unused*/) override { return false; }
|
||||
|
||||
bool PostVisit(LoadCsv &load_csv) override {
|
||||
if (HasSymbolLocalScope(load_csv.row_var_->name_)) {
|
||||
throw RedeclareVariableError(load_csv.row_var_->name_);
|
||||
}
|
||||
load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Return &ret) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_return = true;
|
||||
VisitReturnBody(ret.body_);
|
||||
scope.in_return = false;
|
||||
return false; // We handled the traversal ourselves.
|
||||
}
|
||||
|
||||
bool PostVisit(Return & /*unused*/) override {
|
||||
for (const auto &name_symbol : scopes_.back().symbols) curr_return_names_.insert(name_symbol.first);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(With &with) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_with = true;
|
||||
VisitReturnBody(with.body_, with.where_);
|
||||
scope.in_with = false;
|
||||
return false; // We handled the traversal ourselves.
|
||||
}
|
||||
|
||||
bool PreVisit(Where & /*unused*/) override {
|
||||
scopes_.back().in_where = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Where & /*unused*/) override {
|
||||
scopes_.back().in_where = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Merge & /*unused*/) override {
|
||||
scopes_.back().in_merge = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Merge & /*unused*/) override {
|
||||
scopes_.back().in_merge = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Unwind &unwind) override {
|
||||
const auto &name = unwind.named_expression_->name_;
|
||||
if (HasSymbolLocalScope(name)) {
|
||||
throw RedeclareVariableError(name);
|
||||
}
|
||||
unwind.named_expression_->MapTo(CreateSymbol(name, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Match & /*unused*/) override {
|
||||
scopes_.back().in_match = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Match & /*unused*/) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_match = false;
|
||||
// Check variables in property maps after visiting Match, so that they can
|
||||
// reference symbols out of bind order.
|
||||
for (auto &ident : scope.identifiers_in_match) {
|
||||
if (!HasSymbolLocalScope(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
|
||||
throw UnboundVariableError(ident->name_);
|
||||
ident->MapTo(scope.symbols[ident->name_]);
|
||||
}
|
||||
scope.identifiers_in_match.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Foreach &for_each) override {
|
||||
const auto &name = for_each.named_expression_->name_;
|
||||
scopes_.emplace_back(Scope());
|
||||
scopes_.back().in_foreach = true;
|
||||
for_each.named_expression_->MapTo(
|
||||
CreateSymbol(name, true, Symbol::Type::ANY, for_each.named_expression_->token_position_));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Foreach & /*unused*/) override {
|
||||
scopes_.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expressions
|
||||
ReturnType Visit(Identifier &ident) override {
|
||||
auto &scope = scopes_.back();
|
||||
if (scope.in_skip || scope.in_limit) {
|
||||
throw SemanticException("Variables are not allowed in {}.", scope.in_skip ? "SKIP" : "LIMIT");
|
||||
}
|
||||
Symbol symbol;
|
||||
if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) {
|
||||
// If we are in the pattern, and outside of a node or an edge, the
|
||||
// identifier is the pattern name.
|
||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH);
|
||||
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
|
||||
// Patterns used to create nodes and edges cannot redeclare already
|
||||
// established bindings. Declaration only happens in single node
|
||||
// patterns and in edge patterns. OpenCypher example,
|
||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
||||
// since `n` now references the bound node instead of declaring it.
|
||||
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
auto type = Symbol::Type::VERTEX;
|
||||
if (scope.visiting_edge) {
|
||||
// Edge referencing is not allowed (like in Neo4j):
|
||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
|
||||
if (HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
|
||||
}
|
||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, type);
|
||||
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
|
||||
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) {
|
||||
// Prevent variable path bounds to reference the identifier which is bound
|
||||
// by the variable path itself.
|
||||
throw UnboundVariableError(ident.name_);
|
||||
}
|
||||
// Variables in property maps or bounds of variable length path during MATCH
|
||||
// can reference symbols bound later in the same MATCH. We collect them
|
||||
// here, so that they can be checked after visiting Match.
|
||||
scope.identifiers_in_match.emplace_back(&ident);
|
||||
} else {
|
||||
// Everything else references a bound symbol.
|
||||
if (!HasSymbol(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) throw UnboundVariableError(ident.name_);
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::ANY);
|
||||
}
|
||||
ident.MapTo(symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
ReturnType Visit(PrimitiveLiteral & /*unused*/) override { return true; }
|
||||
|
||||
ReturnType Visit(ParameterLookup & /*unused*/) override { return true; }
|
||||
|
||||
bool PreVisit(Aggregation &aggr) override {
|
||||
auto &scope = scopes_.back();
|
||||
// Check if the aggregation can be used in this context. This check should
|
||||
// probably move to a separate phase, which checks if the query is well
|
||||
// formed.
|
||||
if ((!scope.in_return && !scope.in_with) || scope.in_order_by || scope.in_skip || scope.in_limit ||
|
||||
scope.in_where) {
|
||||
throw SemanticException("Aggregation functions are only allowed in WITH and RETURN.");
|
||||
}
|
||||
if (scope.in_aggregation) {
|
||||
throw SemanticException(
|
||||
"Using aggregation functions inside aggregation functions is not "
|
||||
"allowed.");
|
||||
}
|
||||
if (scope.num_if_operators) {
|
||||
// Neo allows aggregations here and produces very interesting behaviors.
|
||||
// To simplify implementation at this moment we decided to completely
|
||||
// disallow aggregations inside of the CASE.
|
||||
// However, in some cases aggregation makes perfect sense, for example:
|
||||
// CASE count(n) WHEN 10 THEN "YES" ELSE "NO" END.
|
||||
// TODO: Rethink of allowing aggregations in some parts of the CASE
|
||||
// construct.
|
||||
throw SemanticException("Using aggregation functions inside of CASE is not allowed.");
|
||||
}
|
||||
// Create a virtual symbol for aggregation result.
|
||||
// Currently, we only have aggregation operators which return numbers.
|
||||
auto aggr_name = Aggregation::OpToString(aggr.op_) + std::to_string(aggr.symbol_pos_);
|
||||
aggr.MapTo(CreateSymbol(aggr_name, false, Symbol::Type::NUMBER));
|
||||
scope.in_aggregation = true;
|
||||
scope.has_aggregation = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Aggregation & /*unused*/) override {
|
||||
scopes_.back().in_aggregation = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(IfOperator & /*unused*/) override {
|
||||
++scopes_.back().num_if_operators;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(IfOperator & /*unused*/) override {
|
||||
--scopes_.back().num_if_operators;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(All &all) override {
|
||||
all.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(all.where_->expression_, {all.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(Single &single) override {
|
||||
single.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(single.where_->expression_, {single.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(Any &any) override {
|
||||
any.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(any.where_->expression_, {any.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(None &none) override {
|
||||
none.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(none.where_->expression_, {none.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(Reduce &reduce) override {
|
||||
reduce.initializer_->Accept(*this);
|
||||
reduce.list_->Accept(*this);
|
||||
VisitWithIdentifiers(reduce.expression_, {reduce.accumulator_, reduce.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PreVisit(Extract &extract) override {
|
||||
extract.list_->Accept(*this);
|
||||
VisitWithIdentifiers(extract.expression_, {extract.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pattern and its subparts.
|
||||
bool PreVisit(Pattern &pattern) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_pattern = true;
|
||||
if ((scope.in_create || scope.in_merge) && pattern.atoms_.size() == 1U) {
|
||||
MG_ASSERT(utils::IsSubtype(*pattern.atoms_[0], NodeAtom::kType), "Expected a single NodeAtom in Pattern");
|
||||
scope.in_create_node = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostVisit(Pattern & /*unused*/) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_pattern = false;
|
||||
scope.in_create_node = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(NodeAtom &node_atom) override {
|
||||
auto &scope = scopes_.back();
|
||||
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
|
||||
const auto &node_name = node_atom.identifier_->name_;
|
||||
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) {
|
||||
throw SemanticException("Cannot create node '" + node_name +
|
||||
"' with labels or properties, because it is already declared.");
|
||||
}
|
||||
scope.in_pattern_atom_identifier = true;
|
||||
node_atom.identifier_->Accept(*this);
|
||||
scope.in_pattern_atom_identifier = false;
|
||||
};
|
||||
|
||||
scope.in_node_atom = true;
|
||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&node_atom.properties_)) {
|
||||
bool props_or_labels = !properties->empty() || !node_atom.labels_.empty();
|
||||
|
||||
check_node_semantic(props_or_labels);
|
||||
for (auto kv : *properties) {
|
||||
kv.second->Accept(*this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
auto &properties_parameter = std::get<ParameterLookup *>(node_atom.properties_);
|
||||
bool props_or_labels = !properties_parameter || !node_atom.labels_.empty();
|
||||
|
||||
check_node_semantic(props_or_labels);
|
||||
properties_parameter->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PostVisit(NodeAtom & /*unused*/) override {
|
||||
scopes_.back().in_node_atom = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(EdgeAtom &edge_atom) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.visiting_edge = &edge_atom;
|
||||
if (scope.in_create || scope.in_merge) {
|
||||
scope.in_create_edge = true;
|
||||
if (edge_atom.edge_types_.size() != 1U) {
|
||||
throw SemanticException(
|
||||
"A single relationship type must be specified "
|
||||
"when creating an edge.");
|
||||
}
|
||||
if (scope.in_create && // Merge allows bidirectionality
|
||||
edge_atom.direction_ == EdgeAtom::Direction::BOTH) {
|
||||
throw SemanticException(
|
||||
"Bidirectional relationship are not supported "
|
||||
"when creating an edge");
|
||||
}
|
||||
if (edge_atom.IsVariable()) {
|
||||
throw SemanticException(
|
||||
"Variable length relationships are not supported when creating an "
|
||||
"edge.");
|
||||
}
|
||||
}
|
||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&edge_atom.properties_)) {
|
||||
for (auto kv : *properties) {
|
||||
kv.second->Accept(*this);
|
||||
}
|
||||
} else {
|
||||
std::get<ParameterLookup *>(edge_atom.properties_)->Accept(*this);
|
||||
}
|
||||
if (edge_atom.IsVariable()) {
|
||||
scope.in_edge_range = true;
|
||||
if (edge_atom.lower_bound_) {
|
||||
edge_atom.lower_bound_->Accept(*this);
|
||||
}
|
||||
if (edge_atom.upper_bound_) {
|
||||
edge_atom.upper_bound_->Accept(*this);
|
||||
}
|
||||
scope.in_edge_range = false;
|
||||
scope.in_pattern = false;
|
||||
if (edge_atom.filter_lambda_.expression) {
|
||||
VisitWithIdentifiers(edge_atom.filter_lambda_.expression,
|
||||
{edge_atom.filter_lambda_.inner_edge, edge_atom.filter_lambda_.inner_node});
|
||||
} else {
|
||||
// Create inner symbols, but don't bind them in scope, since they are to
|
||||
// be used in the missing filter expression.
|
||||
auto *inner_edge = edge_atom.filter_lambda_.inner_edge;
|
||||
inner_edge->MapTo(
|
||||
symbol_table_->CreateSymbol(inner_edge->name_, inner_edge->user_declared_, Symbol::Type::EDGE));
|
||||
auto *inner_node = edge_atom.filter_lambda_.inner_node;
|
||||
inner_node->MapTo(
|
||||
symbol_table_->CreateSymbol(inner_node->name_, inner_node->user_declared_, Symbol::Type::VERTEX));
|
||||
}
|
||||
if (edge_atom.weight_lambda_.expression) {
|
||||
VisitWithIdentifiers(edge_atom.weight_lambda_.expression,
|
||||
{edge_atom.weight_lambda_.inner_edge, edge_atom.weight_lambda_.inner_node});
|
||||
}
|
||||
scope.in_pattern = true;
|
||||
}
|
||||
scope.in_pattern_atom_identifier = true;
|
||||
edge_atom.identifier_->Accept(*this);
|
||||
scope.in_pattern_atom_identifier = false;
|
||||
if (edge_atom.total_weight_) {
|
||||
if (HasSymbolLocalScope(edge_atom.total_weight_->name_)) {
|
||||
throw RedeclareVariableError(edge_atom.total_weight_->name_);
|
||||
}
|
||||
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope(
|
||||
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PostVisit(EdgeAtom & /*unused*/) override {
|
||||
auto &scope = scopes_.back();
|
||||
scope.visiting_edge = nullptr;
|
||||
scope.in_create_edge = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Scope stores the state of where we are when visiting the AST and a map of
|
||||
// names to symbols.
|
||||
struct Scope {
|
||||
bool in_pattern{false};
|
||||
bool in_merge{false};
|
||||
bool in_create{false};
|
||||
// in_create_node is true if we are creating or merging *only* a node.
|
||||
// Therefore, it is *not* equivalent to (in_create || in_merge) &&
|
||||
// in_node_atom.
|
||||
bool in_create_node{false};
|
||||
// True if creating an edge;
|
||||
// shortcut for (in_create || in_merge) && visiting_edge.
|
||||
bool in_create_edge{false};
|
||||
bool in_node_atom{false};
|
||||
EdgeAtom *visiting_edge{nullptr};
|
||||
bool in_aggregation{false};
|
||||
bool in_return{false};
|
||||
bool in_with{false};
|
||||
bool in_skip{false};
|
||||
bool in_limit{false};
|
||||
bool in_order_by{false};
|
||||
bool in_where{false};
|
||||
bool in_match{false};
|
||||
bool in_foreach{false};
|
||||
// True when visiting a pattern atom (node or edge) identifier, which can be
|
||||
// reused or created in the pattern itself.
|
||||
bool in_pattern_atom_identifier{false};
|
||||
// True when visiting range bounds of a variable path.
|
||||
bool in_edge_range{false};
|
||||
// True if the return/with contains an aggregation in any named expression.
|
||||
bool has_aggregation{false};
|
||||
// Map from variable names to symbols.
|
||||
std::map<std::string, Symbol> symbols;
|
||||
// Identifiers found in property maps of patterns or as variable length path
|
||||
// bounds in a single Match clause. They need to be checked after visiting
|
||||
// Match. Identifiers created by naming vertices, edges and paths are *not*
|
||||
// stored in here.
|
||||
std::vector<Identifier *> identifiers_in_match;
|
||||
// Number of nested IfOperators.
|
||||
int num_if_operators{0};
|
||||
};
|
||||
|
||||
inline static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope,
|
||||
Symbol::Type type) {
|
||||
if (auto it = scope.symbols.find(name); it != scope.symbols.end()) {
|
||||
const auto &symbol = it->second;
|
||||
// Unless we have `ANY` type, check that types match.
|
||||
if (type != Symbol::Type::ANY && symbol.type() != Symbol::Type::ANY && type != symbol.type()) {
|
||||
throw TypeMismatchError(name, Symbol::TypeToString(symbol.type()), Symbol::TypeToString(type));
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool HasSymbol(const std::string &name) const {
|
||||
return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); });
|
||||
}
|
||||
|
||||
bool HasSymbolLocalScope(const std::string &name) const { return scopes_.back().symbols.contains(name); }
|
||||
|
||||
// @return true if it added a predefined identifier with that name
|
||||
bool ConsumePredefinedIdentifier(const std::string &name) {
|
||||
auto it = predefined_identifiers_.find(name);
|
||||
|
||||
if (it == predefined_identifiers_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we can only use the predefined identifier in a single scope so we remove it after creating
|
||||
// a symbol for it
|
||||
auto &identifier = it->second;
|
||||
MG_ASSERT(!identifier->user_declared_, "Predefined symbols cannot be user declared!");
|
||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
||||
predefined_identifiers_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a freshly generated symbol. Previous mapping of the same name to a
|
||||
// different symbol is replaced with the new one.
|
||||
Symbol CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY,
|
||||
int token_position = -1) {
|
||||
auto symbol = symbol_table_->CreateSymbol(name, user_declared, type, token_position);
|
||||
scopes_.back().symbols[name] = symbol;
|
||||
return symbol;
|
||||
}
|
||||
|
||||
Symbol GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY) {
|
||||
// NOLINTNEXTLINE
|
||||
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
|
||||
if (auto maybe_symbol = FindSymbolInScope(name, *scope, type); maybe_symbol) {
|
||||
return *maybe_symbol;
|
||||
}
|
||||
}
|
||||
return CreateSymbol(name, user_declared, type);
|
||||
}
|
||||
|
||||
// Returns the symbol by name. If the mapping already exists, checks if the
|
||||
// types match. Otherwise, returns a new symbol.
|
||||
Symbol GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared,
|
||||
Symbol::Type type = Symbol::Type::ANY) {
|
||||
auto &scope = scopes_.back();
|
||||
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
|
||||
return *maybe_symbol;
|
||||
}
|
||||
return CreateSymbol(name, user_declared, type);
|
||||
}
|
||||
|
||||
void VisitReturnBody(ReturnBody &body, Where *where = nullptr) {
|
||||
auto &scope = scopes_.back();
|
||||
for (auto &expr : body.named_expressions) {
|
||||
expr->Accept(*this);
|
||||
}
|
||||
std::vector<Symbol> user_symbols;
|
||||
if (body.all_identifiers) {
|
||||
// Carry over user symbols because '*' appeared.
|
||||
for (const auto &sym_pair : scope.symbols) {
|
||||
if (!sym_pair.second.user_declared()) {
|
||||
continue;
|
||||
}
|
||||
user_symbols.emplace_back(sym_pair.second);
|
||||
}
|
||||
if (user_symbols.empty()) {
|
||||
throw SemanticException("There are no variables in scope to use for '*'.");
|
||||
}
|
||||
}
|
||||
// WITH/RETURN clause removes declarations of all the previous variables and
|
||||
// declares only those established through named expressions. New declarations
|
||||
// must not be visible inside named expressions themselves.
|
||||
bool removed_old_names = false;
|
||||
if ((!where && body.order_by.empty()) || scope.has_aggregation) {
|
||||
// WHERE and ORDER BY need to see both the old and new symbols, unless we
|
||||
// have an aggregation. Therefore, we can clear the symbols immediately if
|
||||
// there is neither ORDER BY nor WHERE, or we have an aggregation.
|
||||
scope.symbols.clear();
|
||||
removed_old_names = true;
|
||||
}
|
||||
// Create symbols for named expressions.
|
||||
std::unordered_set<std::string> new_names;
|
||||
for (const auto &user_sym : user_symbols) {
|
||||
new_names.insert(user_sym.name());
|
||||
scope.symbols[user_sym.name()] = user_sym;
|
||||
}
|
||||
for (auto &named_expr : body.named_expressions) {
|
||||
const auto &name = named_expr->name_;
|
||||
if (!new_names.insert(name).second) {
|
||||
throw SemanticException("Multiple results with the same name '{}' are not allowed.", name);
|
||||
}
|
||||
// An improvement would be to infer the type of the expression, so that the
|
||||
// new symbol would have a more specific type.
|
||||
named_expr->MapTo(CreateSymbol(name, true, Symbol::Type::ANY, named_expr->token_position_));
|
||||
}
|
||||
scope.in_order_by = true;
|
||||
for (const auto &order_pair : body.order_by) {
|
||||
order_pair.expression->Accept(*this);
|
||||
}
|
||||
scope.in_order_by = false;
|
||||
if (body.skip) {
|
||||
scope.in_skip = true;
|
||||
body.skip->Accept(*this);
|
||||
scope.in_skip = false;
|
||||
}
|
||||
if (body.limit) {
|
||||
scope.in_limit = true;
|
||||
body.limit->Accept(*this);
|
||||
scope.in_limit = false;
|
||||
}
|
||||
if (where) where->Accept(*this);
|
||||
if (!removed_old_names) {
|
||||
// We have an ORDER BY or WHERE, but no aggregation, which means we didn't
|
||||
// clear the old symbols, so do it now. We cannot just call clear, because
|
||||
// we've added new symbols.
|
||||
for (auto sym_it = scope.symbols.begin(); sym_it != scope.symbols.end();) {
|
||||
if (new_names.find(sym_it->first) == new_names.end()) {
|
||||
sym_it = scope.symbols.erase(sym_it);
|
||||
} else {
|
||||
sym_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
scopes_.back().has_aggregation = false;
|
||||
}
|
||||
|
||||
void VisitWithIdentifiers(Expression *expr, const std::vector<Identifier *> &identifiers) {
|
||||
auto &scope = scopes_.back();
|
||||
std::vector<std::pair<std::optional<Symbol>, Identifier *>> prev_symbols;
|
||||
// Collect previous symbols if they exist.
|
||||
for (const auto &identifier : identifiers) {
|
||||
std::optional<Symbol> prev_symbol;
|
||||
auto prev_symbol_it = scope.symbols.find(identifier->name_);
|
||||
if (prev_symbol_it != scope.symbols.end()) {
|
||||
prev_symbol = prev_symbol_it->second;
|
||||
}
|
||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
||||
prev_symbols.emplace_back(prev_symbol, identifier);
|
||||
}
|
||||
// Visit the expression with the new symbols bound.
|
||||
expr->Accept(*this);
|
||||
// Restore back to previous symbols.
|
||||
for (const auto &prev : prev_symbols) {
|
||||
const auto &prev_symbol = prev.first;
|
||||
const auto &identifier = prev.second;
|
||||
if (prev_symbol) {
|
||||
scope.symbols[identifier->name_] = *prev_symbol;
|
||||
} else {
|
||||
scope.symbols.erase(identifier->name_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SymbolTable *symbol_table_;
|
||||
|
||||
// Identifiers which are injected from outside the query. Each identifier
|
||||
// is mapped by its name.
|
||||
std::unordered_map<std::string, Identifier *> predefined_identifiers_;
|
||||
std::vector<Scope> scopes_;
|
||||
std::unordered_set<std::string> prev_return_names_;
|
||||
std::unordered_set<std::string> curr_return_names_;
|
||||
};
|
||||
|
||||
inline SymbolTable MakeSymbolTable(CypherQuery *query, const std::vector<Identifier *> &predefined_identifiers = {}) {
|
||||
SymbolTable symbol_table;
|
||||
SymbolGenerator symbol_generator(&symbol_table, predefined_identifiers);
|
||||
query->single_query_->Accept(symbol_generator);
|
||||
for (auto *cypher_union : query->cypher_unions_) {
|
||||
cypher_union->Accept(symbol_generator);
|
||||
}
|
||||
return symbol_table;
|
||||
}
|
||||
|
||||
} // namespace memgraph::expr
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/shard.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
@@ -64,16 +64,16 @@ query::v2::TypedValue ToTypedValue(const Value &value) {
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const query::v2::VertexAccessor &vertex,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
return ToBoltVertex(vertex.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const query::v2::EdgeAccessor &edge,
|
||||
const storage::v3::Shard &db, storage::v3::View view) {
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
return ToBoltEdge(edge.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Shard &db,
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Storage &db,
|
||||
storage::v3::View view) {
|
||||
switch (value.type()) {
|
||||
case query::v2::TypedValue::Type::Null:
|
||||
@@ -132,9 +132,8 @@ storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const
|
||||
}
|
||||
|
||||
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);
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(vertex.Gid().AsUint());
|
||||
auto maybe_labels = vertex.Labels(view);
|
||||
if (maybe_labels.HasError()) return maybe_labels.GetError();
|
||||
std::vector<std::string> labels;
|
||||
@@ -152,11 +151,10 @@ storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3:
|
||||
}
|
||||
|
||||
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 storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(edge.Gid().AsUint());
|
||||
auto from = communication::bolt::Id::FromUint(edge.FromVertex().Gid().AsUint());
|
||||
auto to = communication::bolt::Id::FromUint(edge.ToVertex().Gid().AsUint());
|
||||
const auto &type = db.EdgeTypeToName(edge.EdgeType());
|
||||
auto maybe_properties = edge.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
@@ -167,7 +165,7 @@ storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::Edg
|
||||
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::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::View view) {
|
||||
std::vector<communication::bolt::Vertex> vertices;
|
||||
vertices.reserve(path.vertices().size());
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
@@ -28,36 +28,36 @@ namespace memgraph::glue::v2 {
|
||||
|
||||
/// @param storage::v3::VertexAccessor for converting to
|
||||
/// communication::bolt::Vertex.
|
||||
/// @param storage::v3::Shard for getting label and property names.
|
||||
/// @param storage::v3::Storage 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 storage::v3::Shard &db, storage::v3::View view);
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
/// @param storage::v3::EdgeAccessor for converting to communication::bolt::Edge.
|
||||
/// @param storage::v3::Shard for getting edge type and property names.
|
||||
/// @param storage::v3::Storage 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 storage::v3::Shard &db, storage::v3::View view);
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
/// @param query::v2::Path for converting to communication::bolt::Path.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Storage 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::Path &path, const storage::v3::Shard &db,
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::View view);
|
||||
|
||||
/// @param query::v2::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::Storage 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 storage::v3::Shard &db, storage::v3::View view);
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
query::v2::TypedValue ToTypedValue(const communication::bolt::Value &value);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <fmt/format.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
namespace memgraph::io {
|
||||
@@ -33,25 +32,9 @@ struct Address {
|
||||
uint16_t last_known_port;
|
||||
|
||||
static Address TestAddress(uint16_t port) {
|
||||
return Address{
|
||||
.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
|
||||
.last_known_port = port,
|
||||
};
|
||||
}
|
||||
|
||||
static Address UniqueLocalAddress() {
|
||||
return Address{
|
||||
.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns a new ID with the same IP and port but a unique UUID.
|
||||
Address ForkUniqueAddress() {
|
||||
return Address{
|
||||
.unique_id = boost::uuids::uuid{boost::uuids::random_generator()()},
|
||||
.last_known_ip = last_known_ip,
|
||||
.last_known_port = last_known_port,
|
||||
};
|
||||
Address ret;
|
||||
ret.last_known_port = port;
|
||||
return ret;
|
||||
}
|
||||
|
||||
friend bool operator==(const Address &lhs, const Address &rhs) = default;
|
||||
|
||||
@@ -99,7 +99,7 @@ class Shared {
|
||||
|
||||
bool IsReady() const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
return item_.has_value();
|
||||
return item_;
|
||||
}
|
||||
|
||||
std::optional<T> TryGet() {
|
||||
@@ -148,6 +148,12 @@ 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_);
|
||||
old.consumed_or_moved_ = true;
|
||||
}
|
||||
|
||||
Future(const Future &) = delete;
|
||||
Future &operator=(const Future &) = delete;
|
||||
~Future() = default;
|
||||
|
||||
@@ -1,35 +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 <random>
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/local_transport/local_transport.hpp"
|
||||
#include "io/local_transport/local_transport_handle.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io::local_transport {
|
||||
|
||||
class LocalSystem {
|
||||
std::shared_ptr<LocalTransportHandle> local_transport_handle_ = std::make_shared<LocalTransportHandle>();
|
||||
|
||||
public:
|
||||
Io<LocalTransport> Register(Address address) {
|
||||
LocalTransport local_transport(local_transport_handle_);
|
||||
return Io{local_transport, address};
|
||||
}
|
||||
|
||||
void ShutDown() { local_transport_handle_->ShutDown(); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::io::local_transport
|
||||
@@ -1,64 +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 <memory>
|
||||
#include <random>
|
||||
#include <utility>
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/local_transport/local_transport_handle.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io::local_transport {
|
||||
|
||||
class LocalTransport {
|
||||
std::shared_ptr<LocalTransportHandle> local_transport_handle_;
|
||||
|
||||
public:
|
||||
explicit LocalTransport(std::shared_ptr<LocalTransportHandle> local_transport_handle)
|
||||
: local_transport_handle_(std::move(local_transport_handle)) {}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
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>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
|
||||
return local_transport_handle_->template Receive<Ms...>(receiver_address, timeout);
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
|
||||
return local_transport_handle_->template Send<M>(to_address, from_address, request_id, std::forward<M>(message));
|
||||
}
|
||||
|
||||
Time Now() const { return local_transport_handle_->Now(); }
|
||||
|
||||
bool ShouldShutDown() const { return local_transport_handle_->ShouldShutDown(); }
|
||||
|
||||
template <class D = std::poisson_distribution<>, class Return = uint64_t>
|
||||
Return Rand(D distrib) {
|
||||
std::random_device rng;
|
||||
return distrib(rng);
|
||||
}
|
||||
};
|
||||
}; // namespace memgraph::io::local_transport
|
||||
@@ -1,151 +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 <condition_variable>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "io/errors.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/time.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io::local_transport {
|
||||
|
||||
class LocalTransportHandle {
|
||||
mutable std::mutex mu_{};
|
||||
mutable std::condition_variable cv_;
|
||||
bool should_shut_down_ = false;
|
||||
|
||||
// the responses to requests that are being waited on
|
||||
std::map<PromiseKey, DeadlineAndOpaquePromise> promises_;
|
||||
|
||||
// messages that are sent to servers that may later receive them
|
||||
std::vector<OpaqueMessage> can_receive_;
|
||||
|
||||
public:
|
||||
~LocalTransportHandle() {
|
||||
for (auto &&[pk, promise] : promises_) {
|
||||
std::move(promise.promise).TimeOut();
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
|
||||
void ShutDown() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
should_shut_down_ = true;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
bool ShouldShutDown() const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
return should_shut_down_;
|
||||
}
|
||||
|
||||
static Time Now() {
|
||||
auto nano_time = std::chrono::system_clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::microseconds>(nano_time);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address /* receiver_address */, Duration timeout) {
|
||||
std::unique_lock lock(mu_);
|
||||
|
||||
Time before = Now();
|
||||
|
||||
spdlog::info("can_receive_ size: {}", can_receive_.size());
|
||||
|
||||
while (can_receive_.empty()) {
|
||||
Time now = Now();
|
||||
|
||||
// protection against non-monotonic timesources
|
||||
auto maxed_now = std::max(now, before);
|
||||
auto elapsed = maxed_now - before;
|
||||
|
||||
if (timeout < elapsed) {
|
||||
return TimedOut{};
|
||||
}
|
||||
|
||||
Duration relative_timeout = timeout - elapsed;
|
||||
|
||||
std::cv_status cv_status_value = cv_.wait_for(lock, relative_timeout);
|
||||
|
||||
if (cv_status_value == std::cv_status::timeout) {
|
||||
return TimedOut{};
|
||||
}
|
||||
}
|
||||
|
||||
auto current_message = std::move(can_receive_.back());
|
||||
can_receive_.pop_back();
|
||||
|
||||
auto m_opt = std::move(current_message).Take<Ms...>();
|
||||
|
||||
return std::move(m_opt).value();
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M &&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)};
|
||||
|
||||
PromiseKey promise_key{
|
||||
.requester_address = to_address, .request_id = opaque_message.request_id, .replier_address = from_address};
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
if (promises_.contains(promise_key)) {
|
||||
spdlog::info("using message to fill promise");
|
||||
// complete waiting promise if it's there
|
||||
DeadlineAndOpaquePromise dop = std::move(promises_.at(promise_key));
|
||||
promises_.erase(promise_key);
|
||||
|
||||
dop.promise.Fill(std::move(opaque_message));
|
||||
} else {
|
||||
spdlog::info("placing message in can_receive_");
|
||||
can_receive_.emplace_back(std::move(opaque_message));
|
||||
}
|
||||
} // lock dropped
|
||||
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
template <Message RequestT, Message 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;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
PromiseKey promise_key{
|
||||
.requester_address = from_address, .request_id = request_id, .replier_address = to_address};
|
||||
OpaquePromise opaque_promise(std::move(promise).ToUnique());
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::io::local_transport
|
||||
@@ -1,46 +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 <variant>
|
||||
|
||||
#include <coordinator/coordinator.hpp>
|
||||
#include <io/rsm/raft.hpp>
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
|
||||
namespace memgraph::io::messages {
|
||||
|
||||
using memgraph::coordinator::CoordinatorReadRequests;
|
||||
using memgraph::coordinator::CoordinatorWriteRequests;
|
||||
using memgraph::coordinator::CoordinatorWriteResponses;
|
||||
using StorageReadRequest = msgs::ReadRequests;
|
||||
using StorageWriteRequest = msgs::WriteRequests;
|
||||
|
||||
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 CoordinatorMessages =
|
||||
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
|
||||
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>;
|
||||
|
||||
using ShardMessages = std::variant<ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, AppendResponse,
|
||||
WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>;
|
||||
|
||||
using ShardManagerMessages = std::variant<WriteResponse<CoordinatorWriteResponses>>;
|
||||
|
||||
} // namespace memgraph::io::messages
|
||||
@@ -1,61 +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 "io/address.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/protobuf_transport/protobuf_transport_handle.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io::protobuf_transport {
|
||||
|
||||
class ProtobufTransport {
|
||||
std::shared_ptr<ProtobufTransportHandle> protobuf_transport_handle_;
|
||||
uint16_t listen_port_;
|
||||
|
||||
public:
|
||||
explicit ProtobufTransport(uint16_t listen_port)
|
||||
: protobuf_transport_handle_(std::make_unique<ProtobufTransportHandle>()), listen_port_(listen_port) {}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, RequestId request_id, RequestT request,
|
||||
Duration timeout) {
|
||||
auto [future, promise] = memgraph::io::FuturePromisePair<ResponseResult<ResponseT>>();
|
||||
|
||||
protobuf_transport_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
|
||||
std::move(promise));
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
|
||||
return protobuf_transport_handle_->template Receive<Ms...>(receiver_address, timeout);
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
|
||||
return protobuf_transport_handle_->template Send<M>(to_address, from_address, request_id, std::forward<M>(message));
|
||||
}
|
||||
|
||||
Time Now() const { return protobuf_transport_handle_->Now(); }
|
||||
|
||||
bool ShouldShutDown() const { return protobuf_transport_handle_->ShouldShutDown(); }
|
||||
|
||||
template <class D = std::poisson_distribution<>, class Return = uint64_t>
|
||||
Return Rand(D distrib) {
|
||||
std::random_device rng;
|
||||
return distrib(rng);
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace memgraph::io::protobuf_transport
|
||||
@@ -1,161 +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 <condition_variable>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "google/protobuf/message.h"
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/transport.hpp"
|
||||
//#include "protobuf/messages.pb.cc"
|
||||
#include "protobuf/messages.pb.h"
|
||||
|
||||
namespace memgraph::io::protobuf_transport {
|
||||
|
||||
using PbAddress = memgraph::protobuf::Address;
|
||||
using memgraph::protobuf::UberMessage;
|
||||
|
||||
class ProtobufTransportHandle {
|
||||
mutable std::mutex mu_{};
|
||||
mutable std::condition_variable cv_;
|
||||
bool should_shut_down_ = false;
|
||||
|
||||
// the responses to requests that are being waited on
|
||||
std::map<PromiseKey, DeadlineAndOpaquePromise> promises_;
|
||||
|
||||
// messages that are sent to servers that may later receive them
|
||||
std::vector<std::string> can_receive_;
|
||||
|
||||
// serialized outbound messages
|
||||
std::vector<std::string> outbox_;
|
||||
|
||||
public:
|
||||
~ProtobufTransportHandle() {
|
||||
for (auto &&[pk, promise] : promises_) {
|
||||
std::move(promise.promise).TimeOut();
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
|
||||
void ShutDown() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
should_shut_down_ = true;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
bool ShouldShutDown() const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
return should_shut_down_;
|
||||
}
|
||||
|
||||
static Time Now() {
|
||||
auto nano_time = std::chrono::system_clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::microseconds>(nano_time);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address /* receiver_address */, Duration timeout) {
|
||||
std::unique_lock lock(mu_);
|
||||
|
||||
Time before = Now();
|
||||
|
||||
spdlog::info("can_receive_ size: {}", can_receive_.size());
|
||||
|
||||
while (can_receive_.empty()) {
|
||||
Time now = Now();
|
||||
|
||||
// protection against non-monotonic timesources
|
||||
auto maxed_now = std::max(now, before);
|
||||
auto elapsed = maxed_now - before;
|
||||
|
||||
if (timeout < elapsed) {
|
||||
return TimedOut{};
|
||||
}
|
||||
|
||||
Duration relative_timeout = timeout - elapsed;
|
||||
|
||||
std::cv_status cv_status_value = cv_.wait_for(lock, relative_timeout);
|
||||
|
||||
if (cv_status_value == std::cv_status::timeout) {
|
||||
return TimedOut{};
|
||||
}
|
||||
}
|
||||
|
||||
auto current_message = std::move(can_receive_.back());
|
||||
can_receive_.pop_back();
|
||||
|
||||
auto m_opt = std::move(current_message).Take<Ms...>();
|
||||
|
||||
return std::move(m_opt).value();
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M &&message) {
|
||||
PromiseKey promise_key{.requester_address = to_address, .request_id = request_id, .replier_address = from_address};
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
if (promises_.contains(promise_key)) {
|
||||
// hair-pin local message optimization
|
||||
spdlog::info("using message to fill local promise");
|
||||
DeadlineAndOpaquePromise dop = std::move(promises_.at(promise_key));
|
||||
promises_.erase(promise_key);
|
||||
|
||||
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)};
|
||||
|
||||
dop.promise.Fill(std::move(opaque_message));
|
||||
} else {
|
||||
spdlog::info("placing message in outbox");
|
||||
|
||||
// serialize protobuf message and place it in the outbox
|
||||
|
||||
std::string bytes;
|
||||
bool success = message.SerializeToString(&bytes);
|
||||
MG_ASSERT(success);
|
||||
|
||||
outbox_.emplace_back(std::move(bytes));
|
||||
}
|
||||
} // lock dropped
|
||||
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
void SubmitRequest(Address to_address, Address from_address, RequestId request_id, RequestT &&request,
|
||||
Duration timeout, ResponsePromise<ResponseT> promise) {
|
||||
const Time deadline = Now() + timeout;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
PromiseKey promise_key{
|
||||
.requester_address = from_address, .request_id = request_id, .replier_address = to_address};
|
||||
OpaquePromise opaque_promise(std::move(promise).ToUnique());
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::io::protobuf_transport
|
||||
@@ -1,25 +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 <condition_variable>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "google/protobuf/message.h"
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io::protobuf_transport {}
|
||||
@@ -14,10 +14,12 @@
|
||||
#include "coordinator/coordinator.hpp"
|
||||
#include "io/rsm/raft.hpp"
|
||||
|
||||
namespace memgraph::coordinator {
|
||||
namespace memgraph::io::rsm {
|
||||
|
||||
// TODO(tyler) don't
|
||||
using namespace memgraph::coordinator;
|
||||
|
||||
template <typename IoImpl>
|
||||
using CoordinatorRsm = memgraph::io::rsm::Raft<IoImpl, Coordinator, CoordinatorWriteRequests, CoordinatorWriteResponses,
|
||||
CoordinatorReadRequests, CoordinatorReadResponses>;
|
||||
using CoordinatorRsm = io::rsm::Raft<IoImpl, Coordinator, WriteRequests, WriteResponses, ReadRequests, ReadResponses>;
|
||||
|
||||
} // namespace memgraph::coordinator
|
||||
} // namespace memgraph::io::rsm
|
||||
@@ -24,35 +24,19 @@
|
||||
|
||||
#include "io/simulator/simulator.hpp"
|
||||
#include "io/transport.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
|
||||
namespace memgraph::io::rsm {
|
||||
|
||||
/// Timeout and replication tunables
|
||||
using namespace std::chrono_literals;
|
||||
static constexpr auto kMinimumElectionTimeout = 100ms;
|
||||
static constexpr auto kMaximumElectionTimeout = 200ms;
|
||||
static constexpr auto kMinimumBroadcastTimeout = 40ms;
|
||||
static constexpr auto kMaximumBroadcastTimeout = 60ms;
|
||||
static constexpr auto kMinimumCronInterval = 1ms;
|
||||
static constexpr auto kMaximumCronInterval = 2ms;
|
||||
static constexpr auto kMinimumReceiveTimeout = 40ms;
|
||||
static constexpr auto kMaximumReceiveTimeout = 60ms;
|
||||
static_assert(kMinimumElectionTimeout > kMaximumBroadcastTimeout,
|
||||
"The broadcast timeout has to be smaller than the election timeout!");
|
||||
static_assert(kMinimumElectionTimeout < kMaximumElectionTimeout,
|
||||
"The minimum election timeout has to be smaller than the maximum election timeout!");
|
||||
static_assert(kMinimumBroadcastTimeout < kMaximumBroadcastTimeout,
|
||||
"The minimum broadcast timeout has to be smaller than the maximum broadcast timeout!");
|
||||
static_assert(kMinimumCronInterval < kMaximumCronInterval,
|
||||
"The minimum cron interval has to be smaller than the maximum cron interval!");
|
||||
static_assert(kMinimumReceiveTimeout < kMaximumReceiveTimeout,
|
||||
"The minimum receive timeout has to be smaller than the maximum receive timeout!");
|
||||
static constexpr size_t kMaximumAppendBatchSize = 1024;
|
||||
using memgraph::io::Address;
|
||||
using memgraph::io::Duration;
|
||||
using memgraph::io::Io;
|
||||
using memgraph::io::ResponseFuture;
|
||||
using memgraph::io::ResponseResult;
|
||||
using memgraph::io::Time;
|
||||
|
||||
using Term = uint64_t;
|
||||
using LogIndex = uint64_t;
|
||||
using LogSize = uint64_t;
|
||||
using RequestId = uint64_t;
|
||||
|
||||
template <typename WriteOperation>
|
||||
struct WriteRequest {
|
||||
@@ -73,7 +57,6 @@ struct WriteResponse {
|
||||
bool success;
|
||||
WriteReturn write_return;
|
||||
std::optional<Address> retry_leader;
|
||||
LogIndex raft_index;
|
||||
};
|
||||
|
||||
template <typename ReadOperation>
|
||||
@@ -99,10 +82,10 @@ struct ReadResponse {
|
||||
template <typename WriteRequest>
|
||||
struct AppendRequest {
|
||||
Term term = 0;
|
||||
LogIndex batch_start_log_index;
|
||||
LogIndex last_log_index;
|
||||
Term last_log_term;
|
||||
std::vector<std::pair<Term, WriteRequest>> entries;
|
||||
LogSize leader_commit;
|
||||
LogIndex leader_commit;
|
||||
};
|
||||
|
||||
struct AppendResponse {
|
||||
@@ -113,18 +96,18 @@ struct AppendResponse {
|
||||
// the leader the offset that we are interested in
|
||||
// to send log offsets from for us. This will only
|
||||
// be useful at the beginning of a leader's term.
|
||||
LogSize log_size;
|
||||
LogIndex last_log_index;
|
||||
};
|
||||
|
||||
struct VoteRequest {
|
||||
Term term = 0;
|
||||
LogSize log_size;
|
||||
LogIndex last_log_index;
|
||||
Term last_log_term;
|
||||
};
|
||||
|
||||
struct VoteResponse {
|
||||
Term term = 0;
|
||||
LogSize committed_log_size;
|
||||
LogIndex committed_log_size;
|
||||
bool vote_granted = false;
|
||||
};
|
||||
|
||||
@@ -132,13 +115,13 @@ template <typename WriteRequest>
|
||||
struct CommonState {
|
||||
Term term = 0;
|
||||
std::vector<std::pair<Term, WriteRequest>> log;
|
||||
LogSize committed_log_size = 0;
|
||||
LogSize applied_size = 0;
|
||||
LogIndex committed_log_size = 0;
|
||||
LogIndex applied_size = 0;
|
||||
};
|
||||
|
||||
struct FollowerTracker {
|
||||
LogIndex next_index = 0;
|
||||
LogSize confirmed_log_size = 0;
|
||||
LogIndex confirmed_contiguous_index = 0;
|
||||
};
|
||||
|
||||
struct PendingClientRequest {
|
||||
@@ -152,36 +135,28 @@ struct Leader {
|
||||
std::unordered_map<LogIndex, PendingClientRequest> pending_client_requests;
|
||||
Time last_broadcast = Time::min();
|
||||
|
||||
std::string static ToString() { return "\tLeader \t"; }
|
||||
std::string ToString() { return "\tLeader \t"; }
|
||||
};
|
||||
|
||||
struct Candidate {
|
||||
std::map<Address, LogSize> successful_votes;
|
||||
std::map<Address, LogIndex> successful_votes;
|
||||
Time election_began = Time::min();
|
||||
std::set<Address> outstanding_votes;
|
||||
|
||||
std::string static ToString() { return "\tCandidate\t"; }
|
||||
std::string ToString() { return "\tCandidate\t"; }
|
||||
};
|
||||
|
||||
struct Follower {
|
||||
Time last_received_append_entries_timestamp;
|
||||
Address leader_address;
|
||||
|
||||
std::string static ToString() { return "\tFollower \t"; }
|
||||
std::string ToString() { return "\tFollower \t"; }
|
||||
};
|
||||
|
||||
using Role = std::variant<Candidate, Leader, Follower>;
|
||||
|
||||
template <typename Role>
|
||||
concept AllRoles = memgraph::utils::SameAsAnyOf<Role, Leader, Follower, Candidate>;
|
||||
|
||||
template <typename Role>
|
||||
concept LeaderOrFollower = memgraph::utils::SameAsAnyOf<Role, Leader, Follower>;
|
||||
|
||||
template <typename Role>
|
||||
concept FollowerOrCandidate = memgraph::utils::SameAsAnyOf<Role, Follower, Candidate>;
|
||||
|
||||
/*
|
||||
|
||||
all ReplicatedState classes should have an Apply method
|
||||
that returns our WriteResponseValue after consensus, and
|
||||
a Read method that returns our ReadResponseValue without
|
||||
@@ -229,63 +204,23 @@ class Raft {
|
||||
Io<IoImpl> io_;
|
||||
std::vector<Address> peers_;
|
||||
ReplicatedState replicated_state_;
|
||||
Time next_cron_;
|
||||
|
||||
public:
|
||||
Raft(Io<IoImpl> &&io, std::vector<Address> peers, ReplicatedState &&replicated_state)
|
||||
: io_(std::forward<Io<IoImpl>>(io)),
|
||||
peers_(peers),
|
||||
replicated_state_(std::forward<ReplicatedState>(replicated_state)) {
|
||||
if (peers.empty()) {
|
||||
role_ = Leader{};
|
||||
}
|
||||
}
|
||||
|
||||
/// Periodic protocol maintenance. Returns the time that Cron should be called again
|
||||
/// in the future.
|
||||
Time Cron() {
|
||||
// dispatch periodic logic based on our role to a specific Cron method.
|
||||
std::optional<Role> new_role = std::visit([&](auto &role) { return Cron(role); }, role_);
|
||||
|
||||
if (new_role) {
|
||||
role_ = std::move(new_role).value();
|
||||
}
|
||||
const Duration random_cron_interval = RandomTimeout(kMinimumCronInterval, kMaximumCronInterval);
|
||||
|
||||
return io_.Now() + random_cron_interval;
|
||||
}
|
||||
|
||||
/// Returns the Address for our underlying Io implementation
|
||||
Address GetAddress() { return io_.GetAddress(); }
|
||||
|
||||
using ReceiveVariant = std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
|
||||
WriteRequest<WriteOperation>, VoteRequest, VoteResponse>;
|
||||
|
||||
void Handle(ReceiveVariant &&message_variant, RequestId request_id, Address from_address) {
|
||||
// dispatch the message to a handler based on our role,
|
||||
// which can be specified in the Handle first argument,
|
||||
// or it can be `auto` if it's a handler for several roles
|
||||
// or messages.
|
||||
std::optional<Role> new_role = std::visit(
|
||||
[&](auto &&msg, auto &role) mutable {
|
||||
return Handle(role, std::forward<decltype(msg)>(msg), request_id, from_address);
|
||||
},
|
||||
std::forward<ReceiveVariant>(message_variant), role_);
|
||||
|
||||
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
|
||||
if (new_role) {
|
||||
role_ = std::move(new_role).value();
|
||||
}
|
||||
}
|
||||
: io_(std::move(io)), peers_(peers), replicated_state_(std::move(replicated_state)) {}
|
||||
|
||||
void Run() {
|
||||
Time last_cron = io_.Now();
|
||||
|
||||
while (!io_.ShouldShutDown()) {
|
||||
const auto now = io_.Now();
|
||||
if (now >= next_cron_) {
|
||||
next_cron_ = Cron();
|
||||
const Duration random_cron_interval = RandomTimeout(1000, 2000);
|
||||
if (now - last_cron > random_cron_interval) {
|
||||
Cron();
|
||||
last_cron = now;
|
||||
}
|
||||
|
||||
const Duration receive_timeout = RandomTimeout(kMinimumReceiveTimeout, kMaximumReceiveTimeout);
|
||||
Duration receive_timeout = RandomTimeout(10000, 50000);
|
||||
|
||||
auto request_result =
|
||||
io_.template ReceiveWithTimeout<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
|
||||
@@ -309,64 +244,58 @@ class Raft {
|
||||
// "Safely replicated" is defined as being known to be present
|
||||
// on at least a majority of all peers (inclusive of the Leader).
|
||||
void BumpCommitIndexAndReplyToClients(Leader &leader) {
|
||||
auto confirmed_log_sizes = std::vector<LogSize>{};
|
||||
auto indices = std::vector<LogIndex>{};
|
||||
|
||||
// We include our own log size in the calculation of the log
|
||||
// confirmed log size that is present on at least a majority of all peers.
|
||||
confirmed_log_sizes.push_back(state_.log.size());
|
||||
// index that is present on at least a majority of all peers.
|
||||
indices.push_back(state_.log.size());
|
||||
|
||||
for (const auto &[addr, f] : leader.followers) {
|
||||
confirmed_log_sizes.push_back(f.confirmed_log_size);
|
||||
Log("Follower at port ", addr.last_known_port, " has confirmed log size of: ", f.confirmed_log_size);
|
||||
indices.push_back(f.confirmed_contiguous_index);
|
||||
Log("at port ", addr.last_known_port, " has confirmed contiguous index of: ", f.confirmed_contiguous_index);
|
||||
}
|
||||
|
||||
// reverse sort from highest to lowest (using std::ranges::greater)
|
||||
std::ranges::sort(confirmed_log_sizes, std::ranges::greater());
|
||||
std::ranges::sort(indices, std::ranges::greater());
|
||||
|
||||
// This is a particularly correctness-critical calculation because it
|
||||
// determines the committed log size that will be broadcast in
|
||||
// the next AppendRequest.
|
||||
// determines which index we will consider to be the committed index.
|
||||
//
|
||||
// If the following sizes are recorded for clusters of different numbers of peers,
|
||||
// these are the expected sizes that are considered to have reached consensus:
|
||||
//
|
||||
// state | expected value | (confirmed_log_sizes.size() / 2)
|
||||
// If the following indexes are recorded for clusters of different sizes,
|
||||
// these are the expected indexes that are considered to have reached
|
||||
// consensus:
|
||||
// state | expected value | (indices.size() / 2)
|
||||
// [1] 1 (1 / 2) => 0
|
||||
// [2, 1] 1 (2 / 2) => 1
|
||||
// [3, 2, 1] 2 (3 / 2) => 1
|
||||
// [4, 3, 2, 1] 2 (4 / 2) => 2
|
||||
// [5, 4, 3, 2, 1] 3 (5 / 2) => 2
|
||||
const size_t majority_index = confirmed_log_sizes.size() / 2;
|
||||
const LogSize new_committed_log_size = confirmed_log_sizes[majority_index];
|
||||
size_t index_present_on_majority = indices.size() / 2;
|
||||
LogIndex new_committed_log_size = indices[index_present_on_majority];
|
||||
|
||||
// We never go backwards in history.
|
||||
MG_ASSERT(state_.committed_log_size <= new_committed_log_size,
|
||||
"as a Leader, we have previously set our committed_log_size to {}, but our Followers have a majority "
|
||||
"committed_log_size of {}",
|
||||
state_.committed_log_size, new_committed_log_size);
|
||||
MG_ASSERT(state_.committed_log_size <= new_committed_log_size);
|
||||
|
||||
state_.committed_log_size = new_committed_log_size;
|
||||
|
||||
// For each size between the old size and the new one (inclusive),
|
||||
// For each index between the old index and the new one (inclusive),
|
||||
// Apply that log's WriteOperation to our replicated_state_,
|
||||
// and use the specific return value of the ReplicatedState::Apply
|
||||
// method (WriteResponseValue) to respond to the requester.
|
||||
for (; state_.applied_size < state_.committed_log_size; state_.applied_size++) {
|
||||
const LogIndex apply_index = state_.applied_size;
|
||||
const auto &write_request = state_.log[apply_index].second;
|
||||
const WriteResponseValue write_return = replicated_state_.Apply(write_request);
|
||||
WriteResponseValue write_return = replicated_state_.Apply(write_request);
|
||||
|
||||
if (leader.pending_client_requests.contains(apply_index)) {
|
||||
const PendingClientRequest client_request = std::move(leader.pending_client_requests.at(apply_index));
|
||||
leader.pending_client_requests.erase(apply_index);
|
||||
PendingClientRequest client_request = std::move(leader.pending_client_requests.at(apply_index));
|
||||
|
||||
const WriteResponse<WriteResponseValue> resp{
|
||||
.success = true,
|
||||
.write_return = std::move(write_return),
|
||||
.raft_index = apply_index,
|
||||
};
|
||||
WriteResponse<WriteResponseValue> resp;
|
||||
resp.success = true;
|
||||
resp.write_return = std::move(write_return);
|
||||
|
||||
io_.Send(client_request.address, client_request.request_id, std::move(resp));
|
||||
leader.pending_client_requests.erase(apply_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,37 +306,31 @@ class Raft {
|
||||
// AppendEntries RPCs are initiated by leaders to replicate log entries and to provide a form of heartbeat
|
||||
void BroadcastAppendEntries(std::map<Address, FollowerTracker> &followers) {
|
||||
for (auto &[address, follower] : followers) {
|
||||
const LogIndex next_index = follower.next_index;
|
||||
|
||||
const auto missing = state_.log.size() - next_index;
|
||||
const auto batch_size = std::min(missing, kMaximumAppendBatchSize);
|
||||
const auto start_index = next_index;
|
||||
const auto end_index = start_index + batch_size;
|
||||
|
||||
// advance follower's next index
|
||||
follower.next_index += batch_size;
|
||||
const LogIndex index = follower.confirmed_contiguous_index;
|
||||
|
||||
std::vector<std::pair<Term, WriteOperation>> entries;
|
||||
|
||||
entries.insert(entries.begin(), state_.log.begin() + start_index, state_.log.begin() + end_index);
|
||||
if (state_.log.size() > index) {
|
||||
entries.insert(entries.begin(), state_.log.begin() + index, state_.log.end());
|
||||
}
|
||||
|
||||
const Term previous_term_from_index = PreviousTermFromIndex(start_index);
|
||||
const Term previous_term_from_index = PreviousTermFromIndex(index);
|
||||
|
||||
Log("sending ", entries.size(), " entries to Follower ", address.last_known_port,
|
||||
" which are above its next_index of ", next_index);
|
||||
" which are above its known index of ", index);
|
||||
|
||||
AppendRequest<WriteOperation> ar{
|
||||
.term = state_.term,
|
||||
.batch_start_log_index = start_index,
|
||||
.last_log_index = index,
|
||||
.last_log_term = previous_term_from_index,
|
||||
.entries = std::move(entries),
|
||||
.entries = entries,
|
||||
.leader_commit = state_.committed_log_size,
|
||||
};
|
||||
|
||||
// request_id not necessary to set because it's not a Future-backed Request.
|
||||
static constexpr RequestId request_id = 0;
|
||||
|
||||
io_.Send(address, request_id, std::move(ar));
|
||||
io_.Send(address, request_id, ar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,15 +339,15 @@ class Raft {
|
||||
Duration RandomTimeout(Duration min, Duration max) {
|
||||
std::uniform_int_distribution time_distrib(min.count(), max.count());
|
||||
|
||||
const auto rand_micros = io_.Rand(time_distrib);
|
||||
auto rand_micros = io_.Rand(time_distrib);
|
||||
|
||||
return Duration{rand_micros};
|
||||
return std::chrono::microseconds{rand_micros};
|
||||
}
|
||||
|
||||
Duration RandomTimeout(int min_micros, int max_micros) {
|
||||
std::uniform_int_distribution time_distrib(min_micros, max_micros);
|
||||
|
||||
const int rand_micros = io_.Rand(time_distrib);
|
||||
int rand_micros = io_.Rand(time_distrib);
|
||||
|
||||
return std::chrono::microseconds{rand_micros};
|
||||
}
|
||||
@@ -438,16 +361,20 @@ class Raft {
|
||||
return term;
|
||||
}
|
||||
|
||||
LogIndex CommittedLogIndex() { return state_.committed_log_size; }
|
||||
|
||||
Term CommittedLogTerm() {
|
||||
MG_ASSERT(state_.log.size() >= state_.committed_log_size);
|
||||
if (state_.log.empty() || state_.committed_log_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto &[term, data] = state_.log.at(state_.committed_log_size - 1);
|
||||
auto &[term, data] = state_.log.at(state_.committed_log_size - 1);
|
||||
return term;
|
||||
}
|
||||
|
||||
LogIndex LastLogIndex() { return state_.log.size(); }
|
||||
|
||||
Term LastLogTerm() const {
|
||||
if (state_.log.empty()) {
|
||||
return 0;
|
||||
@@ -460,19 +387,21 @@ class Raft {
|
||||
template <typename... Ts>
|
||||
void Log(Ts &&...args) {
|
||||
const Time now = io_.Now();
|
||||
const auto micros = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
auto micros = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
|
||||
const Term term = state_.term;
|
||||
const std::string role_string = std::visit([&](const auto &role) { return role.ToString(); }, role_);
|
||||
|
||||
std::ostringstream out;
|
||||
|
||||
out << '\t' << static_cast<int>(micros) << "\t" << term << "\t" << io_.GetAddress().last_known_port;
|
||||
out << '\t' << (int)micros << "\t" << term << "\t" << io_.GetAddress().last_known_port;
|
||||
|
||||
std::string role_string = std::visit([&](auto &&role) { return role.ToString(); }, role_);
|
||||
|
||||
out << role_string;
|
||||
|
||||
(out << ... << args);
|
||||
|
||||
spdlog::info(out.str());
|
||||
spdlog::debug(out.str());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
@@ -486,6 +415,16 @@ class Raft {
|
||||
/// been received.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
/// Periodic protocol maintenance.
|
||||
void Cron() {
|
||||
// dispatch periodic logic based on our role to a specific Cron method.
|
||||
std::optional<Role> new_role = std::visit([&](auto &&role) { return Cron(role); }, role_);
|
||||
|
||||
if (new_role) {
|
||||
role_ = std::move(new_role).value();
|
||||
}
|
||||
}
|
||||
|
||||
// Raft paper - 5.2
|
||||
// Candidates keep sending Vote to peers until:
|
||||
// 1. receiving Append with a higher term (become Follower)
|
||||
@@ -493,17 +432,17 @@ class Raft {
|
||||
// 3. receiving a quorum of responses to our last batch of Vote (become a Leader)
|
||||
std::optional<Role> Cron(Candidate &candidate) {
|
||||
const auto now = io_.Now();
|
||||
const Duration election_timeout = RandomTimeout(kMinimumElectionTimeout, kMaximumElectionTimeout);
|
||||
const Duration election_timeout = RandomTimeout(100000, 200000);
|
||||
const auto election_timeout_us = std::chrono::duration_cast<std::chrono::milliseconds>(election_timeout).count();
|
||||
|
||||
if (now - candidate.election_began > election_timeout) {
|
||||
state_.term++;
|
||||
Log("becoming Candidate for term ", state_.term, " after leader timeout of ", election_timeout_us,
|
||||
"ms elapsed since last election attempt");
|
||||
" elapsed since last election attempt");
|
||||
|
||||
const VoteRequest request{
|
||||
.term = state_.term,
|
||||
.log_size = state_.log.size(),
|
||||
.last_log_index = LastLogIndex(),
|
||||
.last_log_term = LastLogTerm(),
|
||||
};
|
||||
|
||||
@@ -531,10 +470,9 @@ class Raft {
|
||||
std::optional<Role> Cron(Follower &follower) {
|
||||
const auto now = io_.Now();
|
||||
const auto time_since_last_append_entries = now - follower.last_received_append_entries_timestamp;
|
||||
Duration election_timeout = RandomTimeout(100000, 200000);
|
||||
|
||||
// randomized follower timeout
|
||||
const Duration election_timeout = RandomTimeout(kMinimumElectionTimeout, kMaximumElectionTimeout);
|
||||
|
||||
// randomized follower timeout with a range of 100-150ms.
|
||||
if (time_since_last_append_entries > election_timeout) {
|
||||
// become a Candidate if we haven't heard from the Leader after this timeout
|
||||
return Candidate{};
|
||||
@@ -546,7 +484,7 @@ class Raft {
|
||||
// Leaders (re)send AppendRequest to followers.
|
||||
std::optional<Role> Cron(Leader &leader) {
|
||||
const Time now = io_.Now();
|
||||
const Duration broadcast_timeout = RandomTimeout(kMinimumBroadcastTimeout, kMaximumBroadcastTimeout);
|
||||
const Duration broadcast_timeout = RandomTimeout(40000, 60000);
|
||||
|
||||
if (now - leader.last_broadcast > broadcast_timeout) {
|
||||
BroadcastAppendEntries(leader.followers);
|
||||
@@ -568,14 +506,31 @@ class Raft {
|
||||
/// message that has been received.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
void Handle(std::variant<ReadRequest<ReadOperation>, AppendRequest<WriteOperation>, AppendResponse,
|
||||
WriteRequest<WriteOperation>, VoteRequest, VoteResponse> &&message_variant,
|
||||
RequestId request_id, Address from_address) {
|
||||
// dispatch the message to a handler based on our role,
|
||||
// which can be specified in the Handle first argument,
|
||||
// or it can be `auto` if it's a handler for several roles
|
||||
// or messages.
|
||||
std::optional<Role> new_role =
|
||||
std::visit([&](auto &&msg, auto &&role) { return Handle(role, std::move(msg), request_id, from_address); },
|
||||
std::move(message_variant), role_);
|
||||
|
||||
// TODO(tyler) (M3) maybe replace std::visit with get_if for explicit prioritized matching, [[likely]] etc...
|
||||
if (new_role) {
|
||||
role_ = std::move(new_role).value();
|
||||
}
|
||||
}
|
||||
|
||||
// all roles can receive Vote and possibly become a follower
|
||||
template <AllRoles ALL>
|
||||
std::optional<Role> Handle(ALL & /* variable */, VoteRequest &&req, RequestId request_id, Address from_address) {
|
||||
Log("received VoteRequest from ", from_address.last_known_port, " with term ", req.term);
|
||||
template <typename AllRoles>
|
||||
std::optional<Role> Handle(AllRoles &, VoteRequest &&req, RequestId request_id, Address from_address) {
|
||||
Log("received Vote from ", from_address.last_known_port, " with term ", req.term);
|
||||
const bool last_log_term_dominates = req.last_log_term >= LastLogTerm();
|
||||
const bool term_dominates = req.term > state_.term;
|
||||
const bool log_size_dominates = req.log_size >= state_.log.size();
|
||||
const bool new_leader = last_log_term_dominates && term_dominates && log_size_dominates;
|
||||
const bool last_log_index_dominates = req.last_log_index >= LastLogIndex();
|
||||
const bool new_leader = last_log_term_dominates && term_dominates && last_log_index_dominates;
|
||||
|
||||
if (new_leader) {
|
||||
MG_ASSERT(req.term > state_.term);
|
||||
@@ -597,9 +552,7 @@ class Raft {
|
||||
.last_received_append_entries_timestamp = io_.Now(),
|
||||
.leader_address = from_address,
|
||||
};
|
||||
}
|
||||
|
||||
if (term_dominates) {
|
||||
} else if (term_dominates) {
|
||||
Log("received a vote from an inferior candidate. Becoming Candidate");
|
||||
state_.term = std::max(state_.term, req.term) + 1;
|
||||
return Candidate{};
|
||||
@@ -608,7 +561,7 @@ class Raft {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Role> Handle(Candidate &candidate, VoteResponse &&res, RequestId /* variable */, Address from_address) {
|
||||
std::optional<Role> Handle(Candidate &candidate, VoteResponse &&res, RequestId, Address from_address) {
|
||||
Log("received VoteResponse");
|
||||
|
||||
if (!res.vote_granted || res.term != state_.term) {
|
||||
@@ -632,14 +585,14 @@ class Raft {
|
||||
for (const auto &[address, committed_log_size] : candidate.successful_votes) {
|
||||
FollowerTracker follower{
|
||||
.next_index = committed_log_size,
|
||||
.confirmed_log_size = committed_log_size,
|
||||
.confirmed_contiguous_index = committed_log_size,
|
||||
};
|
||||
followers.insert({address, follower});
|
||||
}
|
||||
for (const auto &address : candidate.outstanding_votes) {
|
||||
FollowerTracker follower{
|
||||
.next_index = state_.log.size(),
|
||||
.confirmed_log_size = 0,
|
||||
.confirmed_contiguous_index = 0,
|
||||
};
|
||||
followers.insert({address, follower});
|
||||
}
|
||||
@@ -657,30 +610,27 @@ class Raft {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <LeaderOrFollower LOF>
|
||||
std::optional<Role> Handle(LOF & /* variable */, VoteResponse && /* variable */, RequestId /* variable */,
|
||||
Address /* variable */) {
|
||||
template <typename AllRoles>
|
||||
std::optional<Role> Handle(AllRoles &, VoteResponse &&, RequestId, Address) {
|
||||
Log("non-Candidate received VoteResponse");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <AllRoles ALL>
|
||||
std::optional<Role> Handle(ALL &role, AppendRequest<WriteOperation> &&req, RequestId request_id,
|
||||
template <typename AllRoles>
|
||||
std::optional<Role> Handle(AllRoles &role, AppendRequest<WriteOperation> &&req, RequestId request_id,
|
||||
Address from_address) {
|
||||
// log size starts out as state_.committed_log_size and only if everything is successful do we
|
||||
// switch it to the log length.
|
||||
AppendResponse res{
|
||||
.success = false,
|
||||
.term = state_.term,
|
||||
.last_log_term = CommittedLogTerm(),
|
||||
.log_size = state_.log.size(),
|
||||
.last_log_index = CommittedLogIndex(),
|
||||
};
|
||||
|
||||
if constexpr (std::is_same<ALL, Leader>()) {
|
||||
if constexpr (std::is_same<AllRoles, Leader>()) {
|
||||
MG_ASSERT(req.term != state_.term, "Multiple leaders are acting under the term ", req.term);
|
||||
}
|
||||
|
||||
const bool is_candidate = std::is_same<ALL, Candidate>();
|
||||
const bool is_candidate = std::is_same<AllRoles, Candidate>();
|
||||
const bool is_failed_competitor = is_candidate && req.term == state_.term;
|
||||
const Time now = io_.Now();
|
||||
|
||||
@@ -701,9 +651,7 @@ class Raft {
|
||||
.last_received_append_entries_timestamp = now,
|
||||
.leader_address = from_address,
|
||||
};
|
||||
}
|
||||
|
||||
if (req.term < state_.term) {
|
||||
} else if (req.term < state_.term) {
|
||||
// nack this request from an old leader
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
@@ -711,7 +659,7 @@ class Raft {
|
||||
}
|
||||
|
||||
// at this point, we're dealing with our own leader
|
||||
if constexpr (std::is_same<ALL, Follower>()) {
|
||||
if constexpr (std::is_same<AllRoles, Follower>()) {
|
||||
// small specialization for when we're already a Follower
|
||||
MG_ASSERT(role.leader_address == from_address, "Multiple Leaders are acting under the same term number!");
|
||||
role.last_received_append_entries_timestamp = now;
|
||||
@@ -720,38 +668,33 @@ class Raft {
|
||||
MG_ASSERT(false, "Somehow entered Follower-specific logic as a non-Follower");
|
||||
}
|
||||
|
||||
res.last_log_term = LastLogTerm();
|
||||
res.last_log_index = LastLogIndex();
|
||||
|
||||
Log("returning last_log_index of ", res.last_log_index);
|
||||
|
||||
// Handle steady-state conditions.
|
||||
if (req.batch_start_log_index != state_.log.size()) {
|
||||
Log("req.batch_start_log_index of ", req.batch_start_log_index, " does not match our log size of ",
|
||||
state_.log.size());
|
||||
if (req.last_log_index != LastLogIndex()) {
|
||||
Log("req.last_log_index is above our last applied log index");
|
||||
} else if (req.last_log_term != LastLogTerm()) {
|
||||
Log("req.last_log_term differs from our leader term at that slot, expected: ", LastLogTerm(), " but got ",
|
||||
req.last_log_term);
|
||||
} else {
|
||||
// happy path - Apply log
|
||||
Log("applying batch of ", req.entries.size(), " entries to our log starting at index ",
|
||||
req.batch_start_log_index);
|
||||
Log("applying batch of entries to log of size ", req.entries.size());
|
||||
|
||||
const auto resize_length = req.batch_start_log_index;
|
||||
|
||||
MG_ASSERT(resize_length >= state_.committed_log_size,
|
||||
MG_ASSERT(req.last_log_index >= state_.committed_log_size,
|
||||
"Applied history from Leader which goes back in time from our commit_index");
|
||||
|
||||
// possibly chop-off stuff that was replaced by
|
||||
// things with different terms (we got data that
|
||||
// hasn't reached consensus yet, which is normal)
|
||||
state_.log.resize(resize_length);
|
||||
state_.log.resize(req.last_log_index);
|
||||
|
||||
if (req.entries.size() > 0) {
|
||||
auto &[first_term, op] = req.entries.at(0);
|
||||
MG_ASSERT(LastLogTerm() <= first_term);
|
||||
}
|
||||
|
||||
state_.log.insert(state_.log.end(), std::make_move_iterator(req.entries.begin()),
|
||||
std::make_move_iterator(req.entries.end()));
|
||||
state_.log.insert(state_.log.end(), req.entries.begin(), req.entries.end());
|
||||
|
||||
MG_ASSERT(req.leader_commit >= state_.committed_log_size);
|
||||
state_.committed_log_size = std::min(req.leader_commit, state_.log.size());
|
||||
state_.committed_log_size = std::min(req.leader_commit, LastLogIndex());
|
||||
|
||||
for (; state_.applied_size < state_.committed_log_size; state_.applied_size++) {
|
||||
const auto &write_request = state_.log[state_.applied_size].second;
|
||||
@@ -761,48 +704,35 @@ class Raft {
|
||||
res.success = true;
|
||||
}
|
||||
|
||||
res.last_log_term = LastLogTerm();
|
||||
res.log_size = state_.log.size();
|
||||
|
||||
Log("returning log_size of ", res.log_size);
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Role> Handle(Leader &leader, AppendResponse &&res, RequestId /* variable */, Address from_address) {
|
||||
std::optional<Role> Handle(Leader &leader, AppendResponse &&res, RequestId, Address from_address) {
|
||||
if (res.term != state_.term) {
|
||||
Log("received AppendResponse related to a previous term when we (presumably) were the leader");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// TODO(tyler) when we have dynamic membership, this assert will become incorrect, but we should
|
||||
// keep it in-place until then because it has bug finding value.
|
||||
MG_ASSERT(leader.followers.contains(from_address), "received AppendResponse from unknown Follower");
|
||||
|
||||
// at this point, we know the term matches and we know this Follower
|
||||
|
||||
FollowerTracker &follower = leader.followers.at(from_address);
|
||||
|
||||
if (res.success) {
|
||||
Log("got successful AppendResponse from ", from_address.last_known_port, " with log_size of ", res.log_size);
|
||||
follower.next_index = std::max(follower.next_index, res.log_size);
|
||||
} else if (!leader.followers.contains(from_address)) {
|
||||
Log("received AppendResponse from unknown Follower");
|
||||
MG_ASSERT(false, "received AppendResponse from unknown Follower");
|
||||
} else {
|
||||
Log("got unsuccessful AppendResponse from ", from_address.last_known_port, " with log_size of ", res.log_size);
|
||||
follower.next_index = res.log_size;
|
||||
if (res.success) {
|
||||
Log("got successful AppendResponse from ", from_address.last_known_port, " with last_log_index of ",
|
||||
res.last_log_index);
|
||||
} else {
|
||||
Log("got unsuccessful AppendResponse from ", from_address.last_known_port, " with last_log_index of ",
|
||||
res.last_log_index);
|
||||
}
|
||||
FollowerTracker &follower = leader.followers.at(from_address);
|
||||
follower.next_index = std::max(follower.next_index, res.last_log_index);
|
||||
follower.confirmed_contiguous_index = std::max(follower.confirmed_contiguous_index, res.last_log_index);
|
||||
|
||||
BumpCommitIndexAndReplyToClients(leader);
|
||||
}
|
||||
|
||||
follower.confirmed_log_size = std::max(follower.confirmed_log_size, res.log_size);
|
||||
|
||||
BumpCommitIndexAndReplyToClients(leader);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <FollowerOrCandidate FOC>
|
||||
std::optional<Role> Handle(FOC & /* variable */, AppendResponse && /* variable */, RequestId /* variable */,
|
||||
Address /* variable */) {
|
||||
template <typename AllRoles>
|
||||
std::optional<Role> Handle(AllRoles &, AppendResponse &&, RequestId, Address) {
|
||||
// we used to be the leader, and are getting old delayed responses
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -812,14 +742,13 @@ class Raft {
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
// Leaders are able to immediately respond to the requester (with a ReadResponseValue) applied to the ReplicatedState
|
||||
std::optional<Role> Handle(Leader & /* variable */, ReadRequest<ReadOperation> &&req, RequestId request_id,
|
||||
Address from_address) {
|
||||
std::optional<Role> Handle(Leader &, ReadRequest<ReadOperation> &&req, RequestId request_id, Address from_address) {
|
||||
Log("handling ReadOperation");
|
||||
ReadOperation read_operation = req.operation;
|
||||
|
||||
ReadResponseValue read_return = replicated_state_.Read(read_operation);
|
||||
|
||||
const ReadResponse<ReadResponseValue> resp{
|
||||
ReadResponse<ReadResponseValue> resp{
|
||||
.success = true,
|
||||
.read_return = std::move(read_return),
|
||||
.retry_leader = std::nullopt,
|
||||
@@ -831,29 +760,27 @@ class Raft {
|
||||
}
|
||||
|
||||
// Candidates should respond with a failure, similar to the Candidate + WriteRequest failure below
|
||||
std::optional<Role> Handle(Candidate & /* variable */, ReadRequest<ReadOperation> && /* variable */,
|
||||
RequestId request_id, Address from_address) {
|
||||
std::optional<Role> Handle(Candidate &, ReadRequest<ReadOperation> &&, RequestId request_id, Address from_address) {
|
||||
Log("received ReadOperation - not redirecting because no Leader is known");
|
||||
const ReadResponse<ReadResponseValue> res{
|
||||
.success = false,
|
||||
};
|
||||
auto res = ReadResponse<ReadResponseValue>{};
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
res.success = false;
|
||||
|
||||
Cron();
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Followers should respond with a redirection, similar to the Follower + WriteRequest response below
|
||||
std::optional<Role> Handle(Follower &follower, ReadRequest<ReadOperation> && /* variable */, RequestId request_id,
|
||||
std::optional<Role> Handle(Follower &follower, ReadRequest<ReadOperation> &&, RequestId request_id,
|
||||
Address from_address) {
|
||||
Log("redirecting client to known Leader with port ", follower.leader_address.last_known_port);
|
||||
auto res = ReadResponse<ReadResponseValue>{};
|
||||
|
||||
const ReadResponse<ReadResponseValue> res{
|
||||
.success = false,
|
||||
.retry_leader = follower.leader_address,
|
||||
};
|
||||
res.success = false;
|
||||
Log("redirecting client to known Leader with port ", follower.leader_address.last_known_port);
|
||||
res.retry_leader = follower.leader_address;
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
@@ -865,32 +792,29 @@ class Raft {
|
||||
// server. If the client’s first choice is not the leader, that
|
||||
// server will reject the client’s request and supply information
|
||||
// about the most recent leader it has heard from.
|
||||
std::optional<Role> Handle(Follower &follower, WriteRequest<WriteOperation> && /* variable */, RequestId request_id,
|
||||
std::optional<Role> Handle(Follower &follower, WriteRequest<WriteOperation> &&, RequestId request_id,
|
||||
Address from_address) {
|
||||
Log("redirecting client to known Leader with port ", follower.leader_address.last_known_port);
|
||||
auto res = WriteResponse<WriteResponseValue>{};
|
||||
|
||||
const WriteResponse<WriteResponseValue> res{
|
||||
.success = false,
|
||||
.retry_leader = follower.leader_address,
|
||||
};
|
||||
res.success = false;
|
||||
Log("redirecting client to known Leader with port ", follower.leader_address.last_known_port);
|
||||
res.retry_leader = follower.leader_address;
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Role> Handle(Candidate & /* variable */, WriteRequest<WriteOperation> && /* variable */,
|
||||
RequestId request_id, Address from_address) {
|
||||
std::optional<Role> Handle(Candidate &, WriteRequest<WriteOperation> &&, RequestId request_id, Address from_address) {
|
||||
Log("received WriteRequest - not redirecting because no Leader is known");
|
||||
auto res = WriteResponse<WriteResponseValue>{};
|
||||
|
||||
const WriteResponse<WriteResponseValue> res{
|
||||
.success = false,
|
||||
};
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
res.success = false;
|
||||
|
||||
Cron();
|
||||
|
||||
io_.Send(from_address, request_id, res);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -900,7 +824,6 @@ class Raft {
|
||||
Log("handling WriteRequest");
|
||||
|
||||
// we are the leader. add item to log and send Append to peers
|
||||
MG_ASSERT(state_.term >= LastLogTerm());
|
||||
state_.log.emplace_back(std::pair(state_.term, std::move(req.operation)));
|
||||
|
||||
LogIndex log_index = state_.log.size() - 1;
|
||||
@@ -913,11 +836,7 @@ class Raft {
|
||||
|
||||
leader.pending_client_requests.emplace(log_index, pcr);
|
||||
|
||||
if (peers_.empty()) {
|
||||
BumpCommitIndexAndReplyToClients(leader);
|
||||
} else {
|
||||
BroadcastAppendEntries(leader.followers);
|
||||
}
|
||||
BroadcastAppendEntries(leader.followers);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
@@ -39,8 +37,10 @@ template <typename IoImpl, typename WriteRequestT, typename WriteResponseT, type
|
||||
class RsmClient {
|
||||
using ServerPool = std::vector<Address>;
|
||||
|
||||
Io<IoImpl> io_;
|
||||
IoImpl io_;
|
||||
Address leader_;
|
||||
|
||||
std::mt19937 cli_rng_{0};
|
||||
ServerPool server_addrs_;
|
||||
|
||||
template <typename ResponseT>
|
||||
@@ -51,7 +51,7 @@ class RsmClient {
|
||||
spdlog::debug("client redirected to leader server {}", leader_.ToString());
|
||||
} 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);
|
||||
size_t addr_index = addr_distrib(cli_rng_);
|
||||
leader_ = server_addrs_[addr_index];
|
||||
|
||||
spdlog::debug(
|
||||
@@ -62,7 +62,7 @@ class RsmClient {
|
||||
}
|
||||
|
||||
public:
|
||||
RsmClient(Io<IoImpl> io, Address leader, ServerPool server_addrs)
|
||||
RsmClient(IoImpl io, Address leader, ServerPool server_addrs)
|
||||
: io_{io}, leader_{leader}, server_addrs_{server_addrs} {}
|
||||
|
||||
RsmClient() = delete;
|
||||
@@ -82,6 +82,7 @@ class RsmClient {
|
||||
|
||||
if (response_result.HasError()) {
|
||||
spdlog::debug("client timed out while trying to communicate with leader server {}", leader_.ToString());
|
||||
// continue;
|
||||
return response_result.GetError();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
/// The ShardRsm is a simple in-memory raft-backed kv store that can be used for simple testing
|
||||
/// The StorageRsm is a simple in-memory raft-backed kv store that can be used for simple testing
|
||||
/// and implementation of some query engine logic before storage engines are fully implemented.
|
||||
///
|
||||
/// To implement multiple read and write commands, change the StorageRead* and StorageWrite* requests
|
||||
/// and responses to a std::variant of the different options, and route them to specific handlers in
|
||||
/// the ShardRsm's Read and Apply methods. Remember that Read is called immediately when the Raft
|
||||
/// the StorageRsm's Read and Apply methods. Remember that Read is called immediately when the Raft
|
||||
/// leader receives the request, and does not replicate anything over Raft. Apply is called only
|
||||
/// AFTER the StorageWriteRequest is replicated to a majority of Raft peers, and the result of calling
|
||||
/// ShardRsm::Apply(StorageWriteRequest) is returned to the client that submitted the request.
|
||||
/// StorageRsm::Apply(StorageWriteRequest) is returned to the client that submitted the request.
|
||||
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
@@ -32,21 +32,33 @@
|
||||
#include "coordinator/hybrid_logical_clock.hpp"
|
||||
#include "io/address.hpp"
|
||||
#include "io/rsm/raft.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "io/simulator/simulator.hpp"
|
||||
#include "io/simulator/simulator_transport.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::io::rsm {
|
||||
|
||||
using memgraph::coordinator::Hlc;
|
||||
using memgraph::storage::v3::LabelId;
|
||||
using memgraph::storage::v3::PropertyValue;
|
||||
using memgraph::io::Address;
|
||||
using memgraph::io::Io;
|
||||
using memgraph::io::ResponseEnvelope;
|
||||
using memgraph::io::ResponseFuture;
|
||||
using memgraph::io::ResponseResult;
|
||||
using memgraph::io::rsm::Raft;
|
||||
using memgraph::io::rsm::ReadRequest;
|
||||
using memgraph::io::rsm::ReadResponse;
|
||||
using memgraph::io::rsm::WriteRequest;
|
||||
using memgraph::io::rsm::WriteResponse;
|
||||
using memgraph::io::simulator::Simulator;
|
||||
using memgraph::io::simulator::SimulatorConfig;
|
||||
using memgraph::io::simulator::SimulatorStats;
|
||||
using memgraph::io::simulator::SimulatorTransport;
|
||||
using memgraph::storage::PropertyValue;
|
||||
|
||||
namespace memgraph::io::rsm {
|
||||
|
||||
using ShardRsmKey = std::vector<PropertyValue>;
|
||||
|
||||
struct StorageWriteRequest {
|
||||
LabelId label_id;
|
||||
Hlc transaction_id;
|
||||
ShardRsmKey key;
|
||||
std::optional<int> value;
|
||||
};
|
||||
@@ -59,8 +71,6 @@ struct StorageWriteResponse {
|
||||
};
|
||||
|
||||
struct StorageReadRequest {
|
||||
LabelId label_id;
|
||||
Hlc transaction_id;
|
||||
ShardRsmKey key;
|
||||
};
|
||||
|
||||
@@ -71,14 +81,14 @@ struct StorageReadResponse {
|
||||
std::optional<Hlc> latest_known_shard_map_version{std::nullopt};
|
||||
};
|
||||
|
||||
class ShardRsm {
|
||||
class StorageRsm {
|
||||
std::map<ShardRsmKey, int> state_;
|
||||
ShardRsmKey minimum_key_;
|
||||
std::optional<ShardRsmKey> maximum_key_{std::nullopt};
|
||||
Hlc shard_map_version_;
|
||||
|
||||
// The key is not located in this shard
|
||||
bool IsKeyInRange(const ShardRsmKey &key) const {
|
||||
bool IsKeyInRange(const ShardRsmKey &key) {
|
||||
if (maximum_key_) [[likely]] {
|
||||
return (key >= minimum_key_ && key <= maximum_key_);
|
||||
}
|
||||
@@ -86,14 +96,14 @@ class ShardRsm {
|
||||
}
|
||||
|
||||
public:
|
||||
StorageReadResponse Read(StorageReadRequest request) const {
|
||||
StorageReadResponse Read(StorageReadRequest request) {
|
||||
StorageReadResponse ret;
|
||||
|
||||
if (!IsKeyInRange(request.key)) {
|
||||
ret.latest_known_shard_map_version = shard_map_version_;
|
||||
ret.shard_rsm_success = false;
|
||||
} else if (state_.contains(request.key)) {
|
||||
ret.value = state_.at(request.key);
|
||||
ret.value = state_[request.key];
|
||||
ret.shard_rsm_success = true;
|
||||
} else {
|
||||
ret.shard_rsm_success = false;
|
||||
@@ -145,7 +155,7 @@ class ShardRsm {
|
||||
ret.last_value = std::nullopt;
|
||||
ret.shard_rsm_success = true;
|
||||
|
||||
state_.emplace(request.key, request.value.value());
|
||||
state_.emplace(request.key, std::move(request.value).value());
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -13,35 +13,13 @@
|
||||
|
||||
#include "io/transport.hpp"
|
||||
|
||||
namespace memgraph::io {
|
||||
namespace memgraph::io::simulator {
|
||||
|
||||
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) {
|
||||
if (lhs.requester_address != rhs.requester_address) {
|
||||
return lhs.requester_address < rhs.requester_address;
|
||||
}
|
||||
|
||||
if (lhs.request_id != rhs.request_id) {
|
||||
return lhs.request_id < rhs.request_id;
|
||||
}
|
||||
|
||||
return lhs.replier_address < rhs.replier_address;
|
||||
}
|
||||
};
|
||||
|
||||
struct OpaqueMessage {
|
||||
Address to_address;
|
||||
Address from_address;
|
||||
uint64_t request_id;
|
||||
std::any message;
|
||||
@@ -87,7 +65,6 @@ struct OpaqueMessage {
|
||||
return RequestEnvelope<Ms...>{
|
||||
.message = std::move(*m_opt),
|
||||
.request_id = request_id,
|
||||
.to_address = to_address,
|
||||
.from_address = from_address,
|
||||
};
|
||||
}
|
||||
@@ -122,7 +99,6 @@ class OpaquePromiseTrait : public OpaquePromiseTraitBase {
|
||||
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};
|
||||
auto promise = static_cast<ResponsePromise<T> *>(ptr);
|
||||
auto unique_promise = std::unique_ptr<ResponsePromise<T>>(promise);
|
||||
@@ -198,35 +174,4 @@ class OpaquePromise {
|
||||
}
|
||||
};
|
||||
|
||||
struct DeadlineAndOpaquePromise {
|
||||
Time deadline;
|
||||
OpaquePromise promise;
|
||||
};
|
||||
|
||||
template <class From>
|
||||
std::type_info const &type_info_for_variant(From const &from) {
|
||||
return std::visit([](auto &&x) -> decltype(auto) { return typeid(x); }, from);
|
||||
}
|
||||
|
||||
template <typename From, typename Return, typename Head, typename... Rest>
|
||||
std::optional<Return> ConvertVariantInner(From &&a) {
|
||||
if (typeid(Head) == type_info_for_variant(a)) {
|
||||
Head concrete = std::get<Head>(std::forward<From>(a));
|
||||
return concrete;
|
||||
}
|
||||
|
||||
if constexpr (sizeof...(Rest) > 0) {
|
||||
return ConvertVariantInner<From, Return, Rest...>(std::forward<From>(a));
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// This function converts a variant to another variant holding a subset OR superset of
|
||||
/// possible types.
|
||||
template <class From, class... Ms>
|
||||
requires(sizeof...(Ms) > 0) std::optional<std::variant<Ms...>> ConvertVariant(From &&from) {
|
||||
return ConvertVariantInner<From, std::variant<Ms...>, Ms...>(std::forward<From>(from));
|
||||
}
|
||||
|
||||
} // namespace memgraph::io
|
||||
} // namespace memgraph::io::simulator
|
||||
@@ -38,7 +38,7 @@ void SimulatorHandle::IncrementServerCountAndWaitForQuiescentState(Address addre
|
||||
server_addresses_.insert(address);
|
||||
|
||||
while (true) {
|
||||
const size_t blocked_servers = blocked_on_receive_.size();
|
||||
const size_t blocked_servers = BlockedServers();
|
||||
|
||||
const bool all_servers_blocked = blocked_servers == server_addresses_.size();
|
||||
|
||||
@@ -50,10 +50,22 @@ void SimulatorHandle::IncrementServerCountAndWaitForQuiescentState(Address addre
|
||||
}
|
||||
}
|
||||
|
||||
size_t SimulatorHandle::BlockedServers() {
|
||||
size_t blocked_servers = blocked_on_receive_;
|
||||
|
||||
for (auto &[promise_key, opaque_promise] : promises_) {
|
||||
if (opaque_promise.promise.IsAwaited() && server_addresses_.contains(promise_key.requester_address)) {
|
||||
blocked_servers++;
|
||||
}
|
||||
}
|
||||
|
||||
return blocked_servers;
|
||||
}
|
||||
|
||||
bool SimulatorHandle::MaybeTickSimulator() {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
const size_t blocked_servers = blocked_on_receive_.size();
|
||||
const size_t blocked_servers = BlockedServers();
|
||||
|
||||
if (blocked_servers < server_addresses_.size()) {
|
||||
// we only need to advance the simulator when all
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "io/address.hpp"
|
||||
#include "io/errors.hpp"
|
||||
#include "io/message_conversion.hpp"
|
||||
#include "io/simulator/message_conversion.hpp"
|
||||
#include "io/simulator/simulator_config.hpp"
|
||||
#include "io/simulator/simulator_stats.hpp"
|
||||
#include "io/time.hpp"
|
||||
@@ -32,6 +32,35 @@
|
||||
|
||||
namespace memgraph::io::simulator {
|
||||
|
||||
using memgraph::io::Duration;
|
||||
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) {
|
||||
if (lhs.requester_address != rhs.requester_address) {
|
||||
return lhs.requester_address < rhs.requester_address;
|
||||
}
|
||||
|
||||
if (lhs.request_id != rhs.request_id) {
|
||||
return lhs.request_id < rhs.request_id;
|
||||
}
|
||||
|
||||
return lhs.replier_address < rhs.replier_address;
|
||||
}
|
||||
};
|
||||
|
||||
struct DeadlineAndOpaquePromise {
|
||||
Time deadline;
|
||||
OpaquePromise promise;
|
||||
};
|
||||
|
||||
class SimulatorHandle {
|
||||
mutable std::mutex mu_{};
|
||||
mutable std::condition_variable cv_;
|
||||
@@ -48,24 +77,30 @@ class SimulatorHandle {
|
||||
Time cluster_wide_time_microseconds_;
|
||||
bool should_shut_down_ = false;
|
||||
SimulatorStats stats_;
|
||||
std::set<Address> blocked_on_receive_;
|
||||
size_t blocked_on_receive_ = 0;
|
||||
std::set<Address> server_addresses_;
|
||||
std::mt19937 rng_;
|
||||
SimulatorConfig config_;
|
||||
|
||||
/// Returns the number of servers currently blocked on Receive, plus
|
||||
/// the servers that are blocked on Futures that were created through
|
||||
/// SimulatorTransport::Request.
|
||||
///
|
||||
/// TODO(tyler) investigate whether avoiding consideration of Futures
|
||||
/// increases determinism.
|
||||
size_t BlockedServers();
|
||||
|
||||
void TimeoutPromisesPastDeadline() {
|
||||
const Time now = cluster_wide_time_microseconds_;
|
||||
for (auto it = promises_.begin(); it != promises_.end();) {
|
||||
auto &[promise_key, dop] = *it;
|
||||
|
||||
for (auto &[promise_key, dop] : promises_) {
|
||||
if (dop.deadline < now) {
|
||||
spdlog::info("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(),
|
||||
promise_key.replier_address.ToString());
|
||||
spdlog::debug("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);
|
||||
promises_.erase(promise_key);
|
||||
|
||||
stats_.timed_out_requests++;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,17 +122,14 @@ class SimulatorHandle {
|
||||
bool ShouldShutDown() const;
|
||||
|
||||
template <Message Request, Message Response>
|
||||
void SubmitRequest(Address to_address, Address from_address, RequestId request_id, Request &&request,
|
||||
Duration timeout, ResponsePromise<Response> &&promise) {
|
||||
void SubmitRequest(Address to_address, Address from_address, uint64_t request_id, Request &&request, Duration timeout,
|
||||
ResponsePromise<Response> &&promise) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
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)};
|
||||
OpaqueMessage om{.from_address = from_address, .request_id = request_id, .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, .replier_address = to_address};
|
||||
@@ -115,7 +147,7 @@ class SimulatorHandle {
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(const Address &receiver, Duration timeout) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
|
||||
blocked_on_receive_.emplace(receiver);
|
||||
blocked_on_receive_ += 1;
|
||||
|
||||
const Time deadline = cluster_wide_time_microseconds_ + timeout;
|
||||
|
||||
@@ -130,7 +162,7 @@ class SimulatorHandle {
|
||||
// than asserting that the last item in can_rx matches.
|
||||
auto m_opt = std::move(message).Take<Ms...>();
|
||||
|
||||
blocked_on_receive_.erase(receiver);
|
||||
blocked_on_receive_ -= 1;
|
||||
|
||||
return std::move(m_opt).value();
|
||||
}
|
||||
@@ -144,19 +176,16 @@ class SimulatorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
blocked_on_receive_.erase(receiver);
|
||||
blocked_on_receive_ -= 1;
|
||||
|
||||
return TimedOut{};
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, RequestId request_id, M message) {
|
||||
void Send(Address to_address, Address from_address, uint64_t request_id, M 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)};
|
||||
OpaqueMessage om{.from_address = from_address, .request_id = request_id, .message = std::move(message_any)};
|
||||
in_flight_.emplace_back(std::make_pair(std::move(to_address), std::move(om)));
|
||||
|
||||
stats_.total_messages++;
|
||||
|
||||
@@ -32,27 +32,25 @@ class SimulatorTransport {
|
||||
SimulatorTransport(std::shared_ptr<SimulatorHandle> simulator_handle, Address address, uint64_t seed)
|
||||
: simulator_handle_(simulator_handle), address_(address), rng_(std::mt19937{seed}) {}
|
||||
|
||||
template <Message RequestT, Message ResponseT>
|
||||
ResponseFuture<ResponseT> Request(Address to_address, Address from_address, uint64_t request_id, RequestT request,
|
||||
Duration timeout) {
|
||||
template <Message Request, Message Response>
|
||||
ResponseFuture<Response> Request(Address address, uint64_t request_id, Request 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);
|
||||
memgraph::io::FuturePromisePairWithNotifier<ResponseResult<Response>>(maybe_tick_simulator);
|
||||
|
||||
simulator_handle_->SubmitRequest(to_address, from_address, request_id, std::move(request), timeout,
|
||||
std::move(promise));
|
||||
simulator_handle_->SubmitRequest(address, address_, request_id, std::move(request), timeout, std::move(promise));
|
||||
|
||||
return std::move(future);
|
||||
}
|
||||
|
||||
template <Message... Ms>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Address receiver_address, Duration timeout) {
|
||||
return simulator_handle_->template Receive<Ms...>(receiver_address, timeout);
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive(Duration timeout) {
|
||||
return simulator_handle_->template Receive<Ms...>(address_, timeout);
|
||||
}
|
||||
|
||||
template <Message M>
|
||||
void Send(Address to_address, Address from_address, uint64_t request_id, M message) {
|
||||
return simulator_handle_->template Send<M>(to_address, from_address, request_id, message);
|
||||
void Send(Address address, uint64_t request_id, M message) {
|
||||
return simulator_handle_->template Send<M>(address, address_, request_id, message);
|
||||
}
|
||||
|
||||
Time Now() const { return simulator_handle_->Now(); }
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
namespace memgraph::io {
|
||||
|
||||
using Duration = std::chrono::microseconds;
|
||||
using Time = std::chrono::time_point<std::chrono::system_clock, Duration>;
|
||||
using Time = std::chrono::time_point<std::chrono::local_t, Duration>;
|
||||
|
||||
} // namespace memgraph::io
|
||||
|
||||
@@ -32,13 +32,10 @@ using memgraph::utils::BasicResult;
|
||||
template <typename T>
|
||||
concept Message = std::same_as<T, std::decay_t<T>>;
|
||||
|
||||
using RequestId = uint64_t;
|
||||
|
||||
template <Message M>
|
||||
struct ResponseEnvelope {
|
||||
M message;
|
||||
RequestId request_id;
|
||||
Address to_address;
|
||||
uint64_t request_id;
|
||||
Address from_address;
|
||||
};
|
||||
|
||||
@@ -54,8 +51,7 @@ using ResponsePromise = memgraph::io::Promise<ResponseResult<M>>;
|
||||
template <Message... Ms>
|
||||
struct RequestEnvelope {
|
||||
std::variant<Ms...> message;
|
||||
RequestId request_id;
|
||||
Address to_address;
|
||||
uint64_t request_id;
|
||||
Address from_address;
|
||||
};
|
||||
|
||||
@@ -66,8 +62,8 @@ template <typename I>
|
||||
class Io {
|
||||
I implementation_;
|
||||
Address address_;
|
||||
RequestId request_id_counter_ = 0;
|
||||
Duration default_timeout_ = std::chrono::microseconds{100000};
|
||||
uint64_t request_id_counter_ = 0;
|
||||
Duration default_timeout_ = std::chrono::microseconds{50000};
|
||||
|
||||
public:
|
||||
Io(I io, Address address) : implementation_(io), address_(address) {}
|
||||
@@ -80,29 +76,26 @@ class Io {
|
||||
Duration GetDefaultTimeout() { return default_timeout_; }
|
||||
|
||||
/// 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_id, request, timeout);
|
||||
template <Message Request, Message Response>
|
||||
ResponseFuture<Response> RequestWithTimeout(Address address, Request request, Duration timeout) {
|
||||
const uint64_t request_id = ++request_id_counter_;
|
||||
return implementation_.template Request<Request, Response>(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_;
|
||||
template <Message Request, Message Response>
|
||||
ResponseFuture<Response> Request(Address address, Request request) {
|
||||
const uint64_t 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, request_id,
|
||||
std::move(request), timeout);
|
||||
return implementation_.template Request<Request, Response>(address, request_id, std::move(request), timeout);
|
||||
}
|
||||
|
||||
/// Wait for an explicit number of microseconds for a request of one of the
|
||||
/// provided types to arrive. This tends to be used by servers.
|
||||
template <Message... Ms>
|
||||
RequestResult<Ms...> ReceiveWithTimeout(Duration timeout) {
|
||||
return implementation_.template Receive<Ms...>(address_, timeout);
|
||||
return implementation_.template Receive<Ms...>(timeout);
|
||||
}
|
||||
|
||||
/// Wait the default number of microseconds for a request of one of the
|
||||
@@ -110,16 +103,15 @@ class Io {
|
||||
template <Message... Ms>
|
||||
requires(sizeof...(Ms) > 0) RequestResult<Ms...> Receive() {
|
||||
const Duration timeout = default_timeout_;
|
||||
return implementation_.template Receive<Ms...>(address_, timeout);
|
||||
return implementation_.template Receive<Ms...>(timeout);
|
||||
}
|
||||
|
||||
/// Send a message in a best-effort fashion. This is used for messaging where
|
||||
/// responses are not necessarily expected, and for servers to respond to requests.
|
||||
/// If you need reliable delivery, this must be built on-top. TCP is not enough for most use cases.
|
||||
template <Message M>
|
||||
void Send(Address to_address, RequestId request_id, M message) {
|
||||
Address from_address = address_;
|
||||
return implementation_.template Send<M>(to_address, from_address, request_id, std::move(message));
|
||||
void Send(Address address, uint64_t request_id, M message) {
|
||||
return implementation_.template Send<M>(address, request_id, std::move(message));
|
||||
}
|
||||
|
||||
/// The current system time. This time source should be preferred over any other,
|
||||
@@ -137,8 +129,5 @@ class Io {
|
||||
}
|
||||
|
||||
Address GetAddress() { return address_; }
|
||||
void SetAddress(Address address) { address_ = address; }
|
||||
|
||||
Io<I> ForkLocal() { return Io(implementation_, address_.ForkUniqueAddress()); }
|
||||
};
|
||||
}; // namespace memgraph::io
|
||||
|
||||
@@ -1,42 +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 <boost/asio/ip/tcp.hpp>
|
||||
#include "io/address.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
namespace memgraph::machine_manager {
|
||||
|
||||
using memgraph::io::Address;
|
||||
using memgraph::storage::v3::SchemaProperty;
|
||||
using CompoundKey = std::vector<memgraph::storage::v3::PropertyValue>;
|
||||
|
||||
struct InitialLabelSpace {
|
||||
std::string label_name;
|
||||
std::vector<SchemaProperty> schema;
|
||||
size_t replication_factor;
|
||||
std::vector<CompoundKey> split_points;
|
||||
};
|
||||
|
||||
struct MachineConfig {
|
||||
std::vector<InitialLabelSpace> initial_label_spaces;
|
||||
std::vector<Address> coordinator_addresses;
|
||||
bool is_storage;
|
||||
bool is_coordinator;
|
||||
bool is_query_engine;
|
||||
boost::asio::ip::address listen_ip;
|
||||
uint16_t listen_port;
|
||||
};
|
||||
|
||||
} // namespace memgraph::machine_manager
|
||||
@@ -1,177 +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 <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 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
|
||||
/// operational requirements continue to be met
|
||||
/// * acting as a machine's single caller of Io::Receive
|
||||
/// * routing incoming messages from the Io interface to the
|
||||
/// appropriate Coordinator or to the StorageManager
|
||||
/// (it's not necessary to route anything in this layer
|
||||
/// to the query engine because the query engine only
|
||||
/// communicates using higher-level Futures that will
|
||||
/// be filled immediately when the response comes in
|
||||
/// at the lower-level transport layer.
|
||||
///
|
||||
/// Every storage engine has exactly one RsmEngine.
|
||||
template <typename IoImpl>
|
||||
class MachineManager {
|
||||
io::Io<IoImpl> io_;
|
||||
MachineConfig config_;
|
||||
CoordinatorRsm<IoImpl> coordinator_;
|
||||
ShardManager<IoImpl> shard_manager_;
|
||||
Time next_cron_;
|
||||
|
||||
public:
|
||||
// TODO initialize ShardManager with "real" coordinator addresses instead of io.GetAddress
|
||||
// which is only true for single-machine config.
|
||||
MachineManager(io::Io<IoImpl> io, MachineConfig config, Coordinator coordinator)
|
||||
: io_(io),
|
||||
config_(config),
|
||||
coordinator_{std::move(io.ForkLocal()), {}, std::move(coordinator)},
|
||||
shard_manager_(ShardManager{io.ForkLocal(), coordinator_.GetAddress()}) {}
|
||||
|
||||
Address CoordinatorAddress() { return coordinator_.GetAddress(); }
|
||||
|
||||
void Run() {
|
||||
while (!io_.ShouldShutDown()) {
|
||||
const auto now = io_.Now();
|
||||
|
||||
if (now >= next_cron_) {
|
||||
next_cron_ = Cron();
|
||||
}
|
||||
|
||||
Duration receive_timeout = next_cron_ - now;
|
||||
|
||||
// Note: this parameter pack must be kept in-sync with the ReceiveWithTimeout parameter pack below
|
||||
using AllMessages =
|
||||
std::variant<ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
|
||||
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse,
|
||||
WriteResponse<CoordinatorWriteResponses>, ReadRequest<StorageReadRequest>,
|
||||
AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>;
|
||||
|
||||
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<
|
||||
ReadRequest<CoordinatorReadRequests>, AppendRequest<CoordinatorWriteRequests>, AppendResponse,
|
||||
WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse, WriteResponse<CoordinatorWriteResponses>,
|
||||
ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>, WriteRequest<StorageWriteRequest>>(
|
||||
receive_timeout);
|
||||
|
||||
if (request_result.HasError()) {
|
||||
// time to do Cron
|
||||
spdlog::info("MM got timeout");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &&request_envelope = std::move(request_result.GetValue());
|
||||
|
||||
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_.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>,
|
||||
AppendResponse, WriteRequest<CoordinatorWriteRequests>, VoteRequest, VoteResponse>(
|
||||
std::move(request_envelope.message));
|
||||
|
||||
MG_ASSERT(conversion_attempt.has_value(), "coordinator message conversion failed");
|
||||
|
||||
spdlog::info("got coordinator message");
|
||||
|
||||
CoordinatorMessages &&cm = std::move(conversion_attempt.value());
|
||||
|
||||
coordinator_.Handle(std::forward<CoordinatorMessages>(cm), request_envelope.request_id,
|
||||
request_envelope.from_address);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool to_sm = shard_manager_.GetAddress() == request_envelope.to_address;
|
||||
spdlog::info("smm: {}", shard_manager_.GetAddress().ToString());
|
||||
if (to_sm) {
|
||||
std::optional<ShardManagerMessages> conversion_attempt =
|
||||
ConvertVariant<AllMessages, WriteResponse<CoordinatorWriteResponses>>(std::move(request_envelope.message));
|
||||
|
||||
MG_ASSERT(conversion_attempt.has_value(), "shard manager message conversion failed");
|
||||
|
||||
spdlog::info("got shard manager message");
|
||||
|
||||
ShardManagerMessages &&smm = std::move(conversion_attempt.value());
|
||||
shard_manager_.Receive(std::forward<ShardManagerMessages>(smm), request_envelope.request_id,
|
||||
request_envelope.from_address);
|
||||
continue;
|
||||
}
|
||||
|
||||
// treat this as a message to a specific shard rsm and cast it accordingly
|
||||
|
||||
std::optional<ShardMessages> conversion_attempt =
|
||||
ConvertVariant<AllMessages, ReadRequest<StorageReadRequest>, AppendRequest<StorageWriteRequest>,
|
||||
AppendResponse, WriteRequest<StorageWriteRequest>, VoteRequest, VoteResponse>(
|
||||
std::move(request_envelope.message));
|
||||
|
||||
MG_ASSERT(conversion_attempt.has_value(), "shard rsm message conversion failed for {}",
|
||||
request_envelope.to_address.ToString());
|
||||
|
||||
spdlog::info("got shard rsm message");
|
||||
|
||||
ShardMessages &&sm = std::move(conversion_attempt.value());
|
||||
shard_manager_.Route(std::forward<ShardMessages>(sm), request_envelope.request_id, request_envelope.to_address,
|
||||
request_envelope.from_address);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Time Cron() {
|
||||
spdlog::info("running MachineManager::Cron, address {}", io_.GetAddress().ToString());
|
||||
return shard_manager_.Cron();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memgraph::machine_manager
|
||||
@@ -1,41 +0,0 @@
|
||||
## Generate Antlr openCypher parser
|
||||
|
||||
set(opencypher_frontend ${CMAKE_CURRENT_SOURCE_DIR}/opencypher)
|
||||
set(opencypher_generated ${opencypher_frontend}/generated)
|
||||
set(opencypher_lexer_grammar ${opencypher_frontend}/grammar/MemgraphCypherLexer.g4)
|
||||
set(opencypher_parser_grammar ${opencypher_frontend}/grammar/MemgraphCypher.g4)
|
||||
|
||||
set(antlr_opencypher_generated_src
|
||||
${opencypher_generated}/MemgraphCypherLexer.cpp
|
||||
${opencypher_generated}/MemgraphCypher.cpp
|
||||
${opencypher_generated}/MemgraphCypherBaseVisitor.cpp
|
||||
${opencypher_generated}/MemgraphCypherVisitor.cpp
|
||||
)
|
||||
set(antlr_opencypher_generated_include
|
||||
${opencypher_generated}/MemgraphCypherLexer.h
|
||||
${opencypher_generated}/MemgraphCypher.h
|
||||
${opencypher_generated}/MemgraphCypherBaseVisitor.h
|
||||
${opencypher_generated}/MemgraphCypherVisitor.h
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
||||
-Dlanguage=Cpp -visitor -package antlropencypher
|
||||
-o ${opencypher_generated}
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
DEPENDS
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
${opencypher_frontend}/grammar/CypherLexer.g4
|
||||
${opencypher_frontend}/grammar/Cypher.g4)
|
||||
|
||||
add_custom_target(generated_opencypher_parser
|
||||
DEPENDS ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include})
|
||||
|
||||
add_library(mg-parser STATIC ${antlr_opencypher_generated_src})
|
||||
add_dependencies(mg-parser generated_opencypher_parser)
|
||||
target_include_directories(mg-parser PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(mg-parser antlr4)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package memgraph.protobuf;
|
||||
|
||||
message Address {
|
||||
bytes unique_id = 1;
|
||||
// this is either 4 bytes for ipv4 or 16 bytes for ipv6
|
||||
bytes last_known_ip = 2;
|
||||
uint32 last_known_port = 3;
|
||||
}
|
||||
|
||||
message TestRequest {
|
||||
uint64 request_id = 1;
|
||||
string content = 2;
|
||||
}
|
||||
|
||||
message UberMessage {
|
||||
uint64 request_id = 1;
|
||||
Address to_address = 2;
|
||||
Address from_address = 3;
|
||||
oneof specific_message {
|
||||
TestRequest test_request = 4;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
define_add_lcp(add_lcp_query lcp_query_v2_cpp_files generated_lcp_query_v2_files)
|
||||
|
||||
add_lcp_query(frontend/ast/ast.lcp)
|
||||
add_lcp_query(frontend/semantic/symbol.lcp)
|
||||
add_lcp_query(plan/operator.lcp)
|
||||
|
||||
add_custom_target(generate_lcp_query_v2 DEPENDS ${generated_lcp_query_v2_files})
|
||||
@@ -10,9 +11,14 @@ set(mg_query_v2_sources
|
||||
common.cpp
|
||||
cypher_query_interpreter.cpp
|
||||
dump.cpp
|
||||
frontend/ast/cypher_main_visitor.cpp
|
||||
frontend/ast/pretty_print.cpp
|
||||
frontend/parsing.cpp
|
||||
frontend/semantic/required_privileges.cpp
|
||||
frontend/semantic/symbol_generator.cpp
|
||||
frontend/stripped.cpp
|
||||
interpret/awesome_memgraph_functions.cpp
|
||||
interpret/eval.cpp
|
||||
interpreter.cpp
|
||||
metadata.cpp
|
||||
plan/operator.cpp
|
||||
@@ -23,28 +29,25 @@ 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
|
||||
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)
|
||||
stream/streams.cpp
|
||||
stream/sources.cpp
|
||||
stream/common.cpp
|
||||
trigger.cpp
|
||||
trigger_context.cpp
|
||||
typed_value.cpp)
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_library(mg-query-v2 STATIC ${mg_query_v2_sources})
|
||||
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)
|
||||
target_link_libraries(mg-query-v2 mg-expr)
|
||||
|
||||
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
||||
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
|
||||
@@ -57,3 +60,45 @@ else()
|
||||
endif()
|
||||
|
||||
target_link_libraries(mg-query-v2 Python3::Python)
|
||||
|
||||
# Generate Antlr openCypher parser
|
||||
set(opencypher_frontend ${CMAKE_CURRENT_SOURCE_DIR}/frontend/opencypher)
|
||||
set(opencypher_generated ${opencypher_frontend}/generated)
|
||||
set(opencypher_lexer_grammar ${opencypher_frontend}/grammar/MemgraphCypherLexer.g4)
|
||||
set(opencypher_parser_grammar ${opencypher_frontend}/grammar/MemgraphCypher.g4)
|
||||
|
||||
set(antlr_opencypher_generated_src
|
||||
${opencypher_generated}/MemgraphCypherLexer.cpp
|
||||
${opencypher_generated}/MemgraphCypher.cpp
|
||||
${opencypher_generated}/MemgraphCypherBaseVisitor.cpp
|
||||
${opencypher_generated}/MemgraphCypherVisitor.cpp
|
||||
)
|
||||
set(antlr_opencypher_generated_include
|
||||
${opencypher_generated}/MemgraphCypherLexer.h
|
||||
${opencypher_generated}/MemgraphCypher.h
|
||||
${opencypher_generated}/MemgraphCypherBaseVisitor.h
|
||||
${opencypher_generated}/MemgraphCypherVisitor.h
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
||||
-Dlanguage=Cpp -visitor -package antlropencypher
|
||||
-o ${opencypher_generated}
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
DEPENDS
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
${opencypher_frontend}/grammar/CypherLexer.g4
|
||||
${opencypher_frontend}/grammar/Cypher.g4)
|
||||
|
||||
add_custom_target(generate_opencypher_parser_v2
|
||||
DEPENDS ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include})
|
||||
|
||||
add_library(antlr_opencypher_parser_lib_v2 STATIC ${antlr_opencypher_generated_src})
|
||||
add_dependencies(antlr_opencypher_parser_lib_v2 generate_opencypher_parser_v2)
|
||||
target_link_libraries(antlr_opencypher_parser_lib_v2 antlr4)
|
||||
|
||||
target_link_libraries(mg-query-v2 antlr_opencypher_parser_lib_v2)
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
|
||||
namespace memgraph::query::v2::accessors {
|
||||
EdgeAccessor::EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props)
|
||||
: edge(std::move(edge)), properties(std::move(props)) {}
|
||||
|
||||
uint64_t EdgeAccessor::EdgeType() const { return edge.type.id; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
Value EdgeAccessor::GetProperty(const std::string & /*prop_name*/) const {
|
||||
// TODO(kostasrim) fix this
|
||||
return {};
|
||||
}
|
||||
|
||||
Edge EdgeAccessor::GetEdge() const { return edge; }
|
||||
|
||||
VertexAccessor EdgeAccessor::To() const { return VertexAccessor(Vertex{edge.dst}, {}); }
|
||||
|
||||
VertexAccessor EdgeAccessor::From() const { return VertexAccessor(Vertex{edge.src}, {}); }
|
||||
|
||||
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; }
|
||||
|
||||
bool VertexAccessor::HasLabel(Label &label) const {
|
||||
return std::find_if(vertex.labels.begin(), vertex.labels.end(),
|
||||
[label](const auto &l) { return l.id == label.id; }) != vertex.labels.end();
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
// TODO(kostasrim) Add string mapping
|
||||
return {};
|
||||
// return ValueToTypedValue(properties[prop_name]);
|
||||
}
|
||||
|
||||
msgs::Vertex VertexAccessor::GetVertex() const { return vertex; }
|
||||
|
||||
} // namespace memgraph::query::v2::accessors
|
||||
@@ -1,187 +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 <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace memgraph::query::v2::accessors {
|
||||
|
||||
using Value = memgraph::msgs::Value;
|
||||
using Edge = memgraph::msgs::Edge;
|
||||
using Vertex = memgraph::msgs::Vertex;
|
||||
using Label = memgraph::msgs::Label;
|
||||
using PropertyId = memgraph::msgs::PropertyId;
|
||||
|
||||
class VertexAccessor;
|
||||
|
||||
class EdgeAccessor final {
|
||||
public:
|
||||
EdgeAccessor(Edge edge, std::vector<std::pair<PropertyId, Value>> props);
|
||||
|
||||
uint64_t EdgeType() const;
|
||||
|
||||
std::vector<std::pair<PropertyId, Value>> Properties() const;
|
||||
|
||||
Value GetProperty(const std::string &prop_name) const;
|
||||
|
||||
Edge GetEdge() const;
|
||||
|
||||
// Dummy function
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
inline size_t CypherId() const { return 10; }
|
||||
|
||||
// bool HasSrcAccessor const { return src == nullptr; }
|
||||
// bool HasDstAccessor const { return dst == nullptr; }
|
||||
|
||||
VertexAccessor To() const;
|
||||
VertexAccessor From() const;
|
||||
|
||||
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;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
class VertexAccessor final {
|
||||
public:
|
||||
using PropertyId = msgs::PropertyId;
|
||||
using Label = msgs::Label;
|
||||
VertexAccessor(Vertex v, std::vector<std::pair<PropertyId, Value>> props);
|
||||
|
||||
std::vector<Label> Labels() const;
|
||||
|
||||
bool HasLabel(Label &label) const;
|
||||
|
||||
std::vector<std::pair<PropertyId, Value>> Properties() const;
|
||||
|
||||
Value GetProperty(PropertyId prop_id) const;
|
||||
Value GetProperty(const std::string &prop_name) const;
|
||||
|
||||
msgs::Vertex GetVertex() const;
|
||||
|
||||
// Dummy function
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||
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)))> {
|
||||
// auto maybe_edges = impl_.InEdges(view, edge_types);
|
||||
// if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
// }
|
||||
//
|
||||
// auto InEdges(storage::View view) const { return InEdges(view, {}); }
|
||||
//
|
||||
// auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest)
|
||||
// const
|
||||
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
||||
// auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
|
||||
// if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
// }
|
||||
//
|
||||
// auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
||||
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
||||
// auto maybe_edges = impl_.OutEdges(view, edge_types);
|
||||
// if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
// }
|
||||
//
|
||||
// auto OutEdges(storage::View view) const { return OutEdges(view, {}); }
|
||||
//
|
||||
// auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types,
|
||||
// const VertexAccessor &dest) const
|
||||
// -> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
||||
// auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
|
||||
// if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
// return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
// }
|
||||
|
||||
// storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); }
|
||||
//
|
||||
// storage::Result<size_t> OutDegree(storage::View view) const { return impl_.OutDegree(view); }
|
||||
//
|
||||
|
||||
friend bool operator==(const VertexAccessor &lhs, const VertexAccessor &rhs) {
|
||||
return lhs.vertex == rhs.vertex && lhs.properties == rhs.properties;
|
||||
}
|
||||
|
||||
friend bool operator!=(const VertexAccessor &lhs, const VertexAccessor &rhs) { return !(lhs == rhs); }
|
||||
|
||||
private:
|
||||
Vertex vertex;
|
||||
std::vector<std::pair<PropertyId, Value>> properties;
|
||||
};
|
||||
|
||||
// inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
|
||||
|
||||
// inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
|
||||
|
||||
// Highly mocked interface. Won't work if used.
|
||||
class Path {
|
||||
public:
|
||||
// Empty for now
|
||||
explicit Path(const VertexAccessor & /*vertex*/, utils::MemoryResource *memory = utils::NewDeleteResource())
|
||||
: mem(memory) {}
|
||||
|
||||
template <typename... TOthers>
|
||||
explicit Path(const VertexAccessor &vertex, const TOthers &...others) {}
|
||||
|
||||
template <typename... TOthers>
|
||||
Path(std::allocator_arg_t /*unused*/, utils::MemoryResource *memory, const VertexAccessor &vertex,
|
||||
const TOthers &...others) {}
|
||||
|
||||
Path(const Path & /*other*/) {}
|
||||
|
||||
Path(const Path & /*other*/, utils::MemoryResource *memory) : mem(memory) {}
|
||||
|
||||
Path(Path && /*other*/) noexcept {}
|
||||
|
||||
Path(Path && /*other*/, utils::MemoryResource *memory) : mem(memory) {}
|
||||
Path &operator=(const Path &path) {
|
||||
if (this == &path) {
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Path &operator=(Path &&path) noexcept {
|
||||
if (this == &path) {
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Path() {}
|
||||
|
||||
friend bool operator==(const Path & /*lhs*/, const Path & /*rhs*/) { return true; };
|
||||
utils::MemoryResource *GetMemoryResource() { return mem; }
|
||||
|
||||
private:
|
||||
utils::MemoryResource *mem = utils::NewDeleteResource();
|
||||
};
|
||||
} // namespace memgraph::query::v2::accessors
|
||||
@@ -1,16 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/ast/ast_visitor.hpp"
|
||||
@@ -1,15 +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
|
||||
|
||||
#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)
|
||||
@@ -1,20 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/ast/cypher_main_visitor.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
using CypherMainVisitor = memgraph::expr::CypherMainVisitor;
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -1,44 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/interpret/eval.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/conversions.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
inline const auto lam = [](const auto &val) { return ValueToTypedValue(val); };
|
||||
namespace detail {
|
||||
class Callable {
|
||||
public:
|
||||
auto operator()(const memgraph::storage::v3::PropertyValue &val) const {
|
||||
return memgraph::storage::v3::PropertyToTypedValue<TypedValue>(val);
|
||||
};
|
||||
auto operator()(const msgs::Value &val) const { return ValueToTypedValue(val); };
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
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
|
||||
@@ -1,21 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "expr/interpret/frame.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
using Frame = memgraph::expr::Frame<TypedValue>;
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -1,20 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
using Symbol = memgraph::expr::Symbol;
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -1,16 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol_generator.hpp"
|
||||
@@ -1,20 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/semantic/symbol_table.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
using SymbolTable = memgraph::expr::SymbolTable;
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "expr/typed_value.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/path.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace v2 = memgraph::query::v2;
|
||||
template class TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>;
|
||||
} // namespace memgraph::expr
|
||||
@@ -1,27 +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 "query/v2/bindings/bindings.hpp"
|
||||
|
||||
#include "expr/typed_value.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace v2 = memgraph::query::v2;
|
||||
extern template class memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor,
|
||||
v2::accessors::Path>;
|
||||
} // namespace memgraph::expr
|
||||
namespace memgraph::query::v2 {
|
||||
using TypedValue =
|
||||
memgraph::expr::TypedValueT<v2::accessors::VertexAccessor, v2::accessors::EdgeAccessor, v2::accessors::Path>;
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -18,13 +18,11 @@
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/v2/bindings/symbol.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/path.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
@@ -88,18 +86,6 @@ concept AccessorWithSetProperty = requires(T accessor, const storage::v3::Proper
|
||||
{ accessor.SetProperty(key, new_value) } -> std::same_as<storage::v3::Result<storage::v3::PropertyValue>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept AccessorWithSetPropertyAndValidate = requires(T accessor, const storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue new_value) {
|
||||
{
|
||||
accessor.SetPropertyAndValidate(key, new_value)
|
||||
} -> 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: {
|
||||
@@ -123,14 +109,13 @@ inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_vio
|
||||
*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_MODIFY_PRIMARY_LABEL: {
|
||||
throw SchemaViolationException(fmt::format("Cannot add or remove label :{} since it is a primary label",
|
||||
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)));
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Cannot create vertex with secondary label :{}", dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,12 +137,12 @@ inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) {
|
||||
/// 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>
|
||||
template <AccessorWithSetProperty 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));
|
||||
const auto maybe_old_value = record->SetPropertyAndValidate(key, storage::v3::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); },
|
||||
[&dba](const storage::v3::SchemaViolation &schema_violation) {
|
||||
@@ -168,13 +153,13 @@ storage::v3::PropertyValue PropsSetChecked(T *record, const DbAccessor &dba, con
|
||||
return std::move(*maybe_old_value);
|
||||
} else {
|
||||
// No validation on edge properties
|
||||
const auto maybe_old_value = record->SetProperty(key, storage::v3::TypedToPropertyValue(value));
|
||||
const auto maybe_old_value = record->SetProperty(key, storage::v3::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
HandleErrorOnPropertyUpdate(maybe_old_value.GetError());
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
}
|
||||
} catch (const expr::TypedValueException &) {
|
||||
} catch (const TypedValueException &) {
|
||||
throw QueryRuntimeException("'{}' cannot be used as a property value.", value.type());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/metadata.hpp"
|
||||
#include "query/v2/parameters.hpp"
|
||||
#include "query/v2/plan/profile.hpp"
|
||||
//#include "query/v2/trigger.hpp"
|
||||
#include "query/v2/shard_request_manager.hpp"
|
||||
#include "query/v2/trigger.hpp"
|
||||
#include "utils/async_timer.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
@@ -71,9 +70,8 @@ struct ExecutionContext {
|
||||
plan::ProfilingStats stats;
|
||||
plan::ProfilingStats *stats_root{nullptr};
|
||||
ExecutionStats execution_stats;
|
||||
// TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
std::unique_ptr<msgs::ShardRequestManagerInterface> shard_request_manager{nullptr};
|
||||
};
|
||||
|
||||
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");
|
||||
|
||||
@@ -1,100 +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 "bindings/typed_value.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
#include "query/v2/requests.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
inline TypedValue ValueToTypedValue(const msgs::Value &value) {
|
||||
using Value = msgs::Value;
|
||||
switch (value.type) {
|
||||
case Value::Type::Null:
|
||||
return {};
|
||||
case Value::Type::Bool:
|
||||
return TypedValue(value.bool_v);
|
||||
case Value::Type::Int64:
|
||||
return TypedValue(value.int_v);
|
||||
case Value::Type::Double:
|
||||
return TypedValue(value.double_v);
|
||||
case Value::Type::String:
|
||||
return TypedValue(value.string_v);
|
||||
case Value::Type::List: {
|
||||
const auto &lst = value.list_v;
|
||||
std::vector<TypedValue> dst;
|
||||
dst.reserve(lst.size());
|
||||
for (const auto &elem : lst) {
|
||||
dst.push_back(ValueToTypedValue(elem));
|
||||
}
|
||||
return TypedValue(std::move(dst));
|
||||
}
|
||||
case Value::Type::Map: {
|
||||
const auto &value_map = value.map_v;
|
||||
std::map<std::string, TypedValue> dst;
|
||||
for (const auto &[key, val] : value_map) {
|
||||
dst[key] = ValueToTypedValue(val);
|
||||
}
|
||||
return TypedValue(std::move(dst));
|
||||
}
|
||||
case Value::Type::Vertex:
|
||||
return TypedValue(accessors::VertexAccessor(value.vertex_v, {}));
|
||||
case Value::Type::Edge:
|
||||
return TypedValue(accessors::EdgeAccessor(value.edge_v, {}));
|
||||
case Value::Type::Path:
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Incorrect type in conversion");
|
||||
}
|
||||
|
||||
inline msgs::Value TypedValueToValue(const TypedValue &value) {
|
||||
using Value = msgs::Value;
|
||||
switch (value.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return {};
|
||||
case TypedValue::Type::Bool:
|
||||
return Value(value.ValueBool());
|
||||
case TypedValue::Type::Int:
|
||||
return Value(value.ValueInt());
|
||||
case TypedValue::Type::Double:
|
||||
return Value(value.ValueDouble());
|
||||
case TypedValue::Type::String:
|
||||
return Value(std::string(value.ValueString()));
|
||||
case TypedValue::Type::List: {
|
||||
const auto &lst = value.ValueList();
|
||||
std::vector<Value> dst;
|
||||
dst.reserve(lst.size());
|
||||
for (const auto &elem : lst) {
|
||||
dst.push_back(TypedValueToValue(elem));
|
||||
}
|
||||
return Value(std::move(dst));
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
const auto &value_map = value.ValueMap();
|
||||
std::map<std::string, Value> dst;
|
||||
for (const auto &[key, val] : value_map) {
|
||||
dst[std::string(key)] = TypedValueToValue(val);
|
||||
}
|
||||
return Value(std::move(dst));
|
||||
}
|
||||
case TypedValue::Type::Vertex:
|
||||
return Value(value.ValueVertex().GetVertex());
|
||||
case TypedValue::Type::Edge:
|
||||
return Value(value.ValueEdge().GetEdge());
|
||||
case TypedValue::Type::Path:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw std::runtime_error("Incorrect type in conversion");
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -10,7 +10,6 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/cypher_query_interpreter.hpp"
|
||||
#include "query/v2/bindings/symbol_generator.hpp"
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
|
||||
@@ -47,7 +46,7 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
auto hash = stripped_query.hash();
|
||||
auto accessor = cache->access();
|
||||
auto it = accessor.find(hash);
|
||||
std::unique_ptr<memgraph::frontend::opencypher::Parser<>> parser;
|
||||
std::unique_ptr<frontend::opencypher::Parser> parser;
|
||||
|
||||
// Return a copy of both the AST storage and the query.
|
||||
CachedQuery result;
|
||||
@@ -64,11 +63,11 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
|
||||
if (it == accessor.end()) {
|
||||
try {
|
||||
parser = std::make_unique<memgraph::frontend::opencypher::Parser<>>(stripped_query.query());
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(stripped_query.query());
|
||||
} catch (const SyntaxException &e) {
|
||||
// There is a syntax exception in the stripped query. Re-run the parser
|
||||
// on the original query to get an appropriate error messsage.
|
||||
parser = std::make_unique<memgraph::frontend::opencypher::Parser<>>(query_string);
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(query_string);
|
||||
|
||||
// If an exception was not thrown here, the stripper messed something
|
||||
// up.
|
||||
@@ -77,8 +76,8 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
|
||||
// Convert the ANTLR4 parse tree into an AST.
|
||||
AstStorage ast_storage;
|
||||
expr::ParsingContext context{true};
|
||||
memgraph::expr::CypherMainVisitor visitor(context, &ast_storage);
|
||||
frontend::ParsingContext context{true};
|
||||
frontend::CypherMainVisitor visitor(context, &ast_storage);
|
||||
|
||||
visitor.visit(parser->tree());
|
||||
|
||||
@@ -120,7 +119,7 @@ std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery
|
||||
DbAccessor *db_accessor,
|
||||
const std::vector<Identifier *> &predefined_identifiers) {
|
||||
auto vertex_counts = plan::MakeVertexCountCache(db_accessor);
|
||||
auto symbol_table = expr::MakeSymbolTable(query, predefined_identifiers);
|
||||
auto symbol_table = 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);
|
||||
return std::make_unique<SingleNodeLogicalPlan>(std::move(root), cost, std::move(ast_storage),
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "parser/opencypher/parser.hpp"
|
||||
#include "query/v2/bindings/cypher_main_visitor.hpp"
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
#include "query/v2/config.hpp"
|
||||
#include "query/v2/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/v2/frontend/opencypher/parser.hpp"
|
||||
#include "query/v2/frontend/semantic/required_privileges.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/v2/frontend/stripped.hpp"
|
||||
#include "query/v2/plan/planner.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
@@ -20,7 +19,6 @@
|
||||
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
|
||||
@@ -111,17 +109,13 @@ class VertexAccessor final {
|
||||
|
||||
auto PrimaryLabel(storage::v3::View view) const { return impl_.PrimaryLabel(view); }
|
||||
|
||||
auto PrimaryKey(storage::v3::View view) const { return impl_.PrimaryKey(view); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabelAndValidate(label); }
|
||||
storage::v3::Result<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabel(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.AddLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<bool> RemoveLabel(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
}
|
||||
storage::v3::Result<bool> RemoveLabel(storage::v3::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> RemoveLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
@@ -138,9 +132,9 @@ class VertexAccessor final {
|
||||
return impl_.GetProperty(key, view);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetPropertyAndValidate(key, value);
|
||||
storage::v3::Result<storage::v3::PropertyValue> SetProperty(storage::v3::PropertyId key,
|
||||
const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetPropertyAndValidate(
|
||||
@@ -168,8 +162,7 @@ class VertexAccessor final {
|
||||
auto InEdges(storage::v3::View view, const std::vector<storage::v3::EdgeTypeId> &edge_types,
|
||||
const VertexAccessor &dest) const
|
||||
-> storage::v3::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
||||
const auto dest_id = dest.impl_.Id(view).GetValue();
|
||||
auto maybe_edges = impl_.InEdges(view, edge_types, &dest_id);
|
||||
auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
}
|
||||
@@ -186,8 +179,7 @@ class VertexAccessor final {
|
||||
auto OutEdges(storage::v3::View view, const std::vector<storage::v3::EdgeTypeId> &edge_types,
|
||||
const VertexAccessor &dest) const
|
||||
-> storage::v3::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
||||
const auto dest_id = dest.impl_.Id(view).GetValue();
|
||||
auto maybe_edges = impl_.OutEdges(view, edge_types, &dest_id);
|
||||
auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||
}
|
||||
@@ -196,8 +188,9 @@ class VertexAccessor final {
|
||||
|
||||
storage::v3::Result<size_t> OutDegree(storage::v3::View view) const { return impl_.OutDegree(view); }
|
||||
|
||||
// TODO(jbajic) Fix Remove Gid
|
||||
static int64_t CypherId() { return 1; }
|
||||
int64_t CypherId() const { return impl_.Gid().AsInt(); }
|
||||
|
||||
storage::v3::Gid Gid() const noexcept { return impl_.Gid(); }
|
||||
|
||||
bool operator==(const VertexAccessor &v) const noexcept {
|
||||
static_assert(noexcept(impl_ == v.impl_));
|
||||
@@ -207,19 +200,14 @@ class VertexAccessor final {
|
||||
bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); }
|
||||
};
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnull-dereference"
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker)
|
||||
inline VertexAccessor EdgeAccessor::To() const { return *static_cast<VertexAccessor *>(nullptr); }
|
||||
inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
|
||||
|
||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker)
|
||||
inline VertexAccessor EdgeAccessor::From() const { return *static_cast<VertexAccessor *>(nullptr); }
|
||||
#pragma clang diagnostic pop
|
||||
inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
|
||||
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class DbAccessor final {
|
||||
storage::v3::Shard::Accessor *accessor_;
|
||||
storage::v3::Storage::Accessor *accessor_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::v3::VerticesIterable iterable_;
|
||||
@@ -251,18 +239,16 @@ class DbAccessor final {
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {}
|
||||
explicit DbAccessor(storage::v3::Storage::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);
|
||||
std::optional<VertexAccessor> FindVertex(storage::v3::Gid gid, storage::v3::View view) {
|
||||
auto maybe_vertex = accessor_->FindVertex(gid, view);
|
||||
if (maybe_vertex) return VertexAccessor(*maybe_vertex);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void FinalizeTransaction() { accessor_->FinalizeTransaction(); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view) { return VerticesIterable(accessor_->Vertices(view)); }
|
||||
|
||||
VerticesIterable Vertices(storage::v3::View view, storage::v3::LabelId label) {
|
||||
@@ -284,6 +270,9 @@ class DbAccessor final {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
// TODO Remove when query modules have been fixed
|
||||
[[deprecated]] VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); }
|
||||
|
||||
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) {
|
||||
@@ -296,15 +285,13 @@ class DbAccessor final {
|
||||
|
||||
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);
|
||||
auto maybe_edge = accessor_->CreateEdge(&from->impl_, &to->impl_, edge_type);
|
||||
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());
|
||||
auto res = accessor_->DeleteEdge(&edge->impl_);
|
||||
if (res.HasError()) {
|
||||
return res.GetError();
|
||||
}
|
||||
@@ -355,15 +342,10 @@ class DbAccessor final {
|
||||
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); }
|
||||
@@ -374,7 +356,7 @@ class DbAccessor final {
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
void Commit() { return accessor_->Commit(coordinator::Hlc{}); }
|
||||
utils::BasicResult<storage::v3::ConstraintViolation, void> Commit() { return accessor_->Commit(); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
@@ -405,6 +387,8 @@ class DbAccessor final {
|
||||
|
||||
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
|
||||
|
||||
storage::v3::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
|
||||
|
||||
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
|
||||
|
||||
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
struct DiscardValueResultStream final {
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
#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 "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
@@ -269,6 +269,10 @@ PullPlanDump::PullPlanDump(DbAccessor *dba)
|
||||
CreateLabelIndicesPullChunk(),
|
||||
// Dump all label property indices
|
||||
CreateLabelPropertyIndicesPullChunk(),
|
||||
// Dump all existence constraints
|
||||
CreateExistenceConstraintsPullChunk(),
|
||||
// Dump all unique constraints
|
||||
CreateUniqueConstraintsPullChunk(),
|
||||
// Create internal index for faster edge creation
|
||||
CreateInternalIndexPullChunk(),
|
||||
// Dump all vertices
|
||||
@@ -360,6 +364,60 @@ PullPlanDump::PullChunk PullPlanDump::CreateLabelPropertyIndicesPullChunk() {
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateExistenceConstraintsPullChunk() {
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of constraint vectors
|
||||
if (!constraints_info_) {
|
||||
constraints_info_.emplace(dba_->ListAllConstraints());
|
||||
}
|
||||
|
||||
const auto &existence = constraints_info_->existence;
|
||||
size_t local_counter = 0;
|
||||
while (global_index < existence.size() && (!n || local_counter < *n)) {
|
||||
const auto &constraint = existence[global_index];
|
||||
std::ostringstream os;
|
||||
DumpExistenceConstraint(&os, dba_, constraint.first, constraint.second);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == existence.size()) {
|
||||
return local_counter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
PullPlanDump::PullChunk PullPlanDump::CreateUniqueConstraintsPullChunk() {
|
||||
return [this, global_index = 0U](AnyStream *stream, std::optional<int> n) mutable -> std::optional<size_t> {
|
||||
// Delay the construction of constraint vectors
|
||||
if (!constraints_info_) {
|
||||
constraints_info_.emplace(dba_->ListAllConstraints());
|
||||
}
|
||||
|
||||
const auto &unique = constraints_info_->unique;
|
||||
size_t local_counter = 0;
|
||||
while (global_index < unique.size() && (!n || local_counter < *n)) {
|
||||
const auto &constraint = unique[global_index];
|
||||
std::ostringstream os;
|
||||
DumpUniqueConstraint(&os, dba_, constraint.first, constraint.second);
|
||||
stream->Result({TypedValue(os.str())});
|
||||
|
||||
++global_index;
|
||||
++local_counter;
|
||||
}
|
||||
|
||||
if (global_index == unique.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()) {
|
||||
|
||||
@@ -32,6 +32,7 @@ struct PullPlanDump {
|
||||
query::v2::DbAccessor *dba_ = nullptr;
|
||||
|
||||
std::optional<storage::v3::IndicesInfo> indices_info_ = std::nullopt;
|
||||
std::optional<storage::v3::ConstraintsInfo> constraints_info_ = std::nullopt;
|
||||
|
||||
using VertexAccessorIterable = decltype(std::declval<query::v2::DbAccessor>().Vertices(storage::v3::View::OLD));
|
||||
using VertexAccessorIterableIterator = decltype(std::declval<VertexAccessorIterable>().begin());
|
||||
@@ -54,6 +55,8 @@ struct PullPlanDump {
|
||||
|
||||
PullChunk CreateLabelIndicesPullChunk();
|
||||
PullChunk CreateLabelPropertyIndicesPullChunk();
|
||||
PullChunk CreateExistenceConstraintsPullChunk();
|
||||
PullChunk CreateUniqueConstraintsPullChunk();
|
||||
PullChunk CreateInternalIndexPullChunk();
|
||||
PullChunk CreateVertexPullChunk();
|
||||
PullChunk CreateEdgePullChunk();
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/bindings/ast_visitor.hpp"
|
||||
#include "query/v2/bindings/symbol.hpp"
|
||||
#include "query/v2/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol.hpp"
|
||||
#include "query/v2/interpret/awesome_memgraph_functions.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/path.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/typeinfo.hpp"
|
||||
|
||||
cpp<#
|
||||
@@ -631,7 +630,7 @@ cpp<#
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class primitive-literal (base-literal)
|
||||
((value "TypedValue" :scope :public)
|
||||
((value "::storage::v3::PropertyValue" :scope :public)
|
||||
(token-position :int32_t :scope :public :initval -1
|
||||
:documentation "This field contains token position of literal used to create PrimitiveLiteral object. If PrimitiveLiteral object is not created from query, leave its value at -1."))
|
||||
(:public
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "utils/visitor.hpp"
|
||||
|
||||
namespace MG_INJECTED_NAMESPACE_NAME {
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
// Forward declares for Tree visitors.
|
||||
class CypherQuery;
|
||||
@@ -96,7 +96,7 @@ class VersionQuery;
|
||||
class Foreach;
|
||||
class SchemaQuery;
|
||||
|
||||
using TreeCompositeVisitor = memgraph::utils::CompositeVisitor<
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
|
||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
|
||||
@@ -105,7 +105,7 @@ using TreeCompositeVisitor = memgraph::utils::CompositeVisitor<
|
||||
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
|
||||
RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach>;
|
||||
|
||||
using TreeLeafVisitor = memgraph::utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
|
||||
using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
|
||||
|
||||
class HierarchicalTreeVisitor : public TreeCompositeVisitor, public TreeLeafVisitor {
|
||||
public:
|
||||
@@ -117,7 +117,7 @@ class HierarchicalTreeVisitor : public TreeCompositeVisitor, public TreeLeafVisi
|
||||
|
||||
template <class TResult>
|
||||
class ExpressionVisitor
|
||||
: public memgraph::utils::Visitor<
|
||||
: public utils::Visitor<
|
||||
TResult, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
|
||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
|
||||
@@ -126,10 +126,9 @@ class ExpressionVisitor
|
||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
class QueryVisitor
|
||||
: public memgraph::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
||||
StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
|
||||
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
||||
StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
|
||||
|
||||
} // namespace MG_INJECTED_NAMESPACE_NAME
|
||||
} // namespace memgraph::query::v2
|
||||
2452
src/query/v2/frontend/ast/cypher_main_visitor.cpp
Normal file
2452
src/query/v2/frontend/ast/cypher_main_visitor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
921
src/query/v2/frontend/ast/cypher_main_visitor.hpp
Normal file
921
src/query/v2/frontend/ast/cypher_main_visitor.hpp
Normal file
@@ -0,0 +1,921 @@
|
||||
// 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 <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include <antlr4-runtime.h>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypherBaseVisitor.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::query::v2::frontend {
|
||||
|
||||
using antlropencypher::MemgraphCypher;
|
||||
|
||||
struct ParsingContext {
|
||||
bool is_query_cached = false;
|
||||
};
|
||||
|
||||
class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
public:
|
||||
explicit CypherMainVisitor(ParsingContext context, AstStorage *storage) : context_(context), storage_(storage) {}
|
||||
|
||||
private:
|
||||
Expression *CreateBinaryOperatorByToken(size_t token, Expression *e1, Expression *e2) {
|
||||
switch (token) {
|
||||
case MemgraphCypher::OR:
|
||||
return storage_->Create<OrOperator>(e1, e2);
|
||||
case MemgraphCypher::XOR:
|
||||
return storage_->Create<XorOperator>(e1, e2);
|
||||
case MemgraphCypher::AND:
|
||||
return storage_->Create<AndOperator>(e1, e2);
|
||||
case MemgraphCypher::PLUS:
|
||||
return storage_->Create<AdditionOperator>(e1, e2);
|
||||
case MemgraphCypher::MINUS:
|
||||
return storage_->Create<SubtractionOperator>(e1, e2);
|
||||
case MemgraphCypher::ASTERISK:
|
||||
return storage_->Create<MultiplicationOperator>(e1, e2);
|
||||
case MemgraphCypher::SLASH:
|
||||
return storage_->Create<DivisionOperator>(e1, e2);
|
||||
case MemgraphCypher::PERCENT:
|
||||
return storage_->Create<ModOperator>(e1, e2);
|
||||
case MemgraphCypher::EQ:
|
||||
return storage_->Create<EqualOperator>(e1, e2);
|
||||
case MemgraphCypher::NEQ1:
|
||||
case MemgraphCypher::NEQ2:
|
||||
return storage_->Create<NotEqualOperator>(e1, e2);
|
||||
case MemgraphCypher::LT:
|
||||
return storage_->Create<LessOperator>(e1, e2);
|
||||
case MemgraphCypher::GT:
|
||||
return storage_->Create<GreaterOperator>(e1, e2);
|
||||
case MemgraphCypher::LTE:
|
||||
return storage_->Create<LessEqualOperator>(e1, e2);
|
||||
case MemgraphCypher::GTE:
|
||||
return storage_->Create<GreaterEqualOperator>(e1, e2);
|
||||
default:
|
||||
throw utils::NotYetImplemented("binary operator");
|
||||
}
|
||||
}
|
||||
|
||||
Expression *CreateUnaryOperatorByToken(size_t token, Expression *e) {
|
||||
switch (token) {
|
||||
case MemgraphCypher::NOT:
|
||||
return storage_->Create<NotOperator>(e);
|
||||
case MemgraphCypher::PLUS:
|
||||
return storage_->Create<UnaryPlusOperator>(e);
|
||||
case MemgraphCypher::MINUS:
|
||||
return storage_->Create<UnaryMinusOperator>(e);
|
||||
default:
|
||||
throw utils::NotYetImplemented("unary operator");
|
||||
}
|
||||
}
|
||||
|
||||
auto ExtractOperators(std::vector<antlr4::tree::ParseTree *> &all_children,
|
||||
const std::vector<size_t> &allowed_operators) {
|
||||
std::vector<size_t> operators;
|
||||
for (auto *child : all_children) {
|
||||
antlr4::tree::TerminalNode *operator_node = nullptr;
|
||||
if ((operator_node = dynamic_cast<antlr4::tree::TerminalNode *>(child))) {
|
||||
if (std::find(allowed_operators.begin(), allowed_operators.end(), operator_node->getSymbol()->getType()) !=
|
||||
allowed_operators.end()) {
|
||||
operators.push_back(operator_node->getSymbol()->getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
return operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert opencypher's n-ary production to ast binary operators.
|
||||
*
|
||||
* @param _expressions Subexpressions of child for which we construct ast
|
||||
* operators, for example expression6 if we want to create ast nodes for
|
||||
* expression7.
|
||||
*/
|
||||
template <typename TExpression>
|
||||
Expression *LeftAssociativeOperatorExpression(std::vector<TExpression *> _expressions,
|
||||
std::vector<antlr4::tree::ParseTree *> all_children,
|
||||
const std::vector<size_t> &allowed_operators) {
|
||||
DMG_ASSERT(_expressions.size(), "can't happen");
|
||||
std::vector<Expression *> expressions;
|
||||
auto operators = ExtractOperators(all_children, allowed_operators);
|
||||
|
||||
for (auto *expression : _expressions) {
|
||||
expressions.push_back(std::any_cast<Expression *>(expression->accept(this)));
|
||||
}
|
||||
|
||||
Expression *first_operand = expressions[0];
|
||||
for (int i = 1; i < (int)expressions.size(); ++i) {
|
||||
first_operand = CreateBinaryOperatorByToken(operators[i - 1], first_operand, expressions[i]);
|
||||
}
|
||||
return first_operand;
|
||||
}
|
||||
|
||||
template <typename TExpression>
|
||||
Expression *PrefixUnaryOperator(TExpression *_expression, std::vector<antlr4::tree::ParseTree *> all_children,
|
||||
const std::vector<size_t> &allowed_operators) {
|
||||
DMG_ASSERT(_expression, "can't happen");
|
||||
auto operators = ExtractOperators(all_children, allowed_operators);
|
||||
|
||||
Expression *expression = std::any_cast<Expression *>(_expression->accept(this));
|
||||
for (int i = (int)operators.size() - 1; i >= 0; --i) {
|
||||
expression = CreateUnaryOperatorByToken(operators[i], expression);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CypherQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCypherQuery(MemgraphCypher::CypherQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return IndexQuery*
|
||||
*/
|
||||
antlrcpp::Any visitIndexQuery(MemgraphCypher::IndexQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ExplainQuery*
|
||||
*/
|
||||
antlrcpp::Any visitExplainQuery(MemgraphCypher::ExplainQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ProfileQuery*
|
||||
*/
|
||||
antlrcpp::Any visitProfileQuery(MemgraphCypher::ProfileQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return InfoQuery*
|
||||
*/
|
||||
antlrcpp::Any visitInfoQuery(MemgraphCypher::InfoQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Constraint
|
||||
*/
|
||||
antlrcpp::Any visitConstraint(MemgraphCypher::ConstraintContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ConstraintQuery*
|
||||
*/
|
||||
antlrcpp::Any visitConstraintQuery(MemgraphCypher::ConstraintQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitAuthQuery(MemgraphCypher::AuthQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return DumpQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDumpQuery(MemgraphCypher::DumpQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitReplicationQuery(MemgraphCypher::ReplicationQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSetReplicationRole(MemgraphCypher::SetReplicationRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterReplica(MemgraphCypher::RegisterReplicaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDropReplica(MemgraphCypher::DropReplicaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return LockPathQuery*
|
||||
*/
|
||||
antlrcpp::Any visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return LoadCsvQuery*
|
||||
*/
|
||||
antlrcpp::Any visitLoadCsv(MemgraphCypher::LoadCsvContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return FreeMemoryQuery*
|
||||
*/
|
||||
antlrcpp::Any visitFreeMemoryQuery(MemgraphCypher::FreeMemoryQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return TriggerQuery*
|
||||
*/
|
||||
antlrcpp::Any visitTriggerQuery(MemgraphCypher::TriggerQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CreateTrigger*
|
||||
*/
|
||||
antlrcpp::Any visitCreateTrigger(MemgraphCypher::CreateTriggerContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return DropTrigger*
|
||||
*/
|
||||
antlrcpp::Any visitDropTrigger(MemgraphCypher::DropTriggerContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowTriggers*
|
||||
*/
|
||||
antlrcpp::Any visitShowTriggers(MemgraphCypher::ShowTriggersContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return IsolationLevelQuery*
|
||||
*/
|
||||
antlrcpp::Any visitIsolationLevelQuery(MemgraphCypher::IsolationLevelQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CreateSnapshotQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCreateSnapshotQuery(MemgraphCypher::CreateSnapshotQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitStreamQuery(MemgraphCypher::StreamQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCreateStream(MemgraphCypher::CreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitConfigKeyValuePair(MemgraphCypher::ConfigKeyValuePairContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitConfigMap(MemgraphCypher::ConfigMapContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitKafkaCreateStream(MemgraphCypher::KafkaCreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitKafkaCreateStreamConfig(MemgraphCypher::KafkaCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitPulsarCreateStreamConfig(MemgraphCypher::PulsarCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitPulsarCreateStream(MemgraphCypher::PulsarCreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCommonCreateStreamConfig(MemgraphCypher::CommonCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDropStream(MemgraphCypher::DropStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitStartStream(MemgraphCypher::StartStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitStartAllStreams(MemgraphCypher::StartAllStreamsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitStopStream(MemgraphCypher::StopStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitStopAllStreams(MemgraphCypher::StopAllStreamsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowStreams(MemgraphCypher::ShowStreamsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCheckStream(MemgraphCypher::CheckStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SettingQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSettingQuery(MemgraphCypher::SettingQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SetSetting*
|
||||
*/
|
||||
antlrcpp::Any visitSetSetting(MemgraphCypher::SetSettingContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowSetting*
|
||||
*/
|
||||
antlrcpp::Any visitShowSetting(MemgraphCypher::ShowSettingContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowSettings*
|
||||
*/
|
||||
antlrcpp::Any visitShowSettings(MemgraphCypher::ShowSettingsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return VersionQuery*
|
||||
*/
|
||||
antlrcpp::Any visitVersionQuery(MemgraphCypher::VersionQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CypherUnion*
|
||||
*/
|
||||
antlrcpp::Any visitCypherUnion(MemgraphCypher::CypherUnionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SingleQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSingleQuery(MemgraphCypher::SingleQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Clause* or vector<Clause*>!!!
|
||||
*/
|
||||
antlrcpp::Any visitClause(MemgraphCypher::ClauseContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Match*
|
||||
*/
|
||||
antlrcpp::Any visitCypherMatch(MemgraphCypher::CypherMatchContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Create*
|
||||
*/
|
||||
antlrcpp::Any visitCreate(MemgraphCypher::CreateContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CallProcedure*
|
||||
*/
|
||||
antlrcpp::Any visitCallProcedure(MemgraphCypher::CallProcedureContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return std::string
|
||||
*/
|
||||
antlrcpp::Any visitUserOrRoleName(MemgraphCypher::UserOrRoleNameContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCreateRole(MemgraphCypher::CreateRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDropRole(MemgraphCypher::DropRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowRoles(MemgraphCypher::ShowRolesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return IndexQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCreateIndex(MemgraphCypher::CreateIndexContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return DropIndex*
|
||||
*/
|
||||
antlrcpp::Any visitDropIndex(MemgraphCypher::DropIndexContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCreateUser(MemgraphCypher::CreateUserContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSetPassword(MemgraphCypher::SetPasswordContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDropUser(MemgraphCypher::DropUserContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowUsers(MemgraphCypher::ShowUsersContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSetRole(MemgraphCypher::SetRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitClearRole(MemgraphCypher::ClearRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitGrantPrivilege(MemgraphCypher::GrantPrivilegeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDenyPrivilege(MemgraphCypher::DenyPrivilegeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRevokePrivilege(MemgraphCypher::RevokePrivilegeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery::Privilege
|
||||
*/
|
||||
antlrcpp::Any visitPrivilege(MemgraphCypher::PrivilegeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowRoleForUser(MemgraphCypher::ShowRoleForUserContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return AuthQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowUsersForRole(MemgraphCypher::ShowUsersForRoleContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Return*
|
||||
*/
|
||||
antlrcpp::Any visitCypherReturn(MemgraphCypher::CypherReturnContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Return*
|
||||
*/
|
||||
antlrcpp::Any visitReturnBody(MemgraphCypher::ReturnBodyContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return pair<bool, vector<NamedExpression*>> first member is true if
|
||||
* asterisk was found in return
|
||||
* expressions.
|
||||
*/
|
||||
antlrcpp::Any visitReturnItems(MemgraphCypher::ReturnItemsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<NamedExpression*>
|
||||
*/
|
||||
antlrcpp::Any visitReturnItem(MemgraphCypher::ReturnItemContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<SortItem>
|
||||
*/
|
||||
antlrcpp::Any visitOrder(MemgraphCypher::OrderContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SortItem
|
||||
*/
|
||||
antlrcpp::Any visitSortItem(MemgraphCypher::SortItemContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return NodeAtom*
|
||||
*/
|
||||
antlrcpp::Any visitNodePattern(MemgraphCypher::NodePatternContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<LabelIx>
|
||||
*/
|
||||
antlrcpp::Any visitNodeLabels(MemgraphCypher::NodeLabelsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return unordered_map<PropertyIx, Expression*>
|
||||
*/
|
||||
antlrcpp::Any visitProperties(MemgraphCypher::PropertiesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return map<std::string, Expression*>
|
||||
*/
|
||||
antlrcpp::Any visitMapLiteral(MemgraphCypher::MapLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<Expression*>
|
||||
*/
|
||||
antlrcpp::Any visitListLiteral(MemgraphCypher::ListLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return PropertyIx
|
||||
*/
|
||||
antlrcpp::Any visitPropertyKeyName(MemgraphCypher::PropertyKeyNameContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
antlrcpp::Any visitSymbolicName(MemgraphCypher::SymbolicNameContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<Pattern*>
|
||||
*/
|
||||
antlrcpp::Any visitPattern(MemgraphCypher::PatternContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Pattern*
|
||||
*/
|
||||
antlrcpp::Any visitPatternPart(MemgraphCypher::PatternPartContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Pattern*
|
||||
*/
|
||||
antlrcpp::Any visitPatternElement(MemgraphCypher::PatternElementContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<pair<EdgeAtom*, NodeAtom*>>
|
||||
*/
|
||||
antlrcpp::Any visitPatternElementChain(MemgraphCypher::PatternElementChainContext *ctx) override;
|
||||
|
||||
/**
|
||||
*@return EdgeAtom*
|
||||
*/
|
||||
antlrcpp::Any visitRelationshipPattern(MemgraphCypher::RelationshipPatternContext *ctx) override;
|
||||
|
||||
/**
|
||||
* This should never be called. Everything is done directly in
|
||||
* visitRelationshipPattern.
|
||||
*/
|
||||
antlrcpp::Any visitRelationshipDetail(MemgraphCypher::RelationshipDetailContext *ctx) override;
|
||||
|
||||
/**
|
||||
* This should never be called. Everything is done directly in
|
||||
* visitRelationshipPattern.
|
||||
*/
|
||||
antlrcpp::Any visitRelationshipLambda(MemgraphCypher::RelationshipLambdaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return vector<EdgeTypeIx>
|
||||
*/
|
||||
antlrcpp::Any visitRelationshipTypes(MemgraphCypher::RelationshipTypesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return std::tuple<EdgeAtom::Type, int64_t, int64_t>.
|
||||
*/
|
||||
antlrcpp::Any visitVariableExpansion(MemgraphCypher::VariableExpansionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Top level expression, does nothing.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression(MemgraphCypher::ExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* OR.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression12(MemgraphCypher::Expression12Context *ctx) override;
|
||||
|
||||
/**
|
||||
* XOR.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression11(MemgraphCypher::Expression11Context *ctx) override;
|
||||
|
||||
/**
|
||||
* AND.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression10(MemgraphCypher::Expression10Context *ctx) override;
|
||||
|
||||
/**
|
||||
* NOT.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression9(MemgraphCypher::Expression9Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Comparisons.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression8(MemgraphCypher::Expression8Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Never call this. Everything related to generating code for comparison
|
||||
* operators should be done in visitExpression8.
|
||||
*/
|
||||
antlrcpp::Any visitPartialComparisonExpression(MemgraphCypher::PartialComparisonExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Addition and subtraction.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression7(MemgraphCypher::Expression7Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Multiplication, division, modding.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression6(MemgraphCypher::Expression6Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Power.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression5(MemgraphCypher::Expression5Context *ctx) override;
|
||||
|
||||
/**
|
||||
* Unary minus and plus.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression4(MemgraphCypher::Expression4Context *ctx) override;
|
||||
|
||||
/**
|
||||
* IS NULL, IS NOT NULL, STARTS WITH, END WITH, =~, ...
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression3a(MemgraphCypher::Expression3aContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Does nothing, everything is done in visitExpression3a.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitStringAndNullOperators(MemgraphCypher::StringAndNullOperatorsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* List indexing and slicing.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression3b(MemgraphCypher::Expression3bContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Does nothing, everything is done in visitExpression3b.
|
||||
*/
|
||||
antlrcpp::Any visitListIndexingOrSlicing(MemgraphCypher::ListIndexingOrSlicingContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Node labels test.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression2a(MemgraphCypher::Expression2aContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Property lookup.
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitExpression2b(MemgraphCypher::Expression2bContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Literals, params, list comprehension...
|
||||
*
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitAtom(MemgraphCypher::AtomContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ParameterLookup*
|
||||
*/
|
||||
antlrcpp::Any visitParameter(MemgraphCypher::ParameterContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitParenthesizedExpression(MemgraphCypher::ParenthesizedExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitFunctionInvocation(MemgraphCypher::FunctionInvocationContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return string - uppercased
|
||||
*/
|
||||
antlrcpp::Any visitFunctionName(MemgraphCypher::FunctionNameContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Expression*
|
||||
*/
|
||||
antlrcpp::Any visitLiteral(MemgraphCypher::LiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Convert escaped string from a query to unescaped utf8 string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
antlrcpp::Any visitStringLiteral(const std::string &escaped);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
antlrcpp::Any visitBooleanLiteral(MemgraphCypher::BooleanLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return TypedValue with either double or int
|
||||
*/
|
||||
antlrcpp::Any visitNumberLiteral(MemgraphCypher::NumberLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return int64_t
|
||||
*/
|
||||
antlrcpp::Any visitIntegerLiteral(MemgraphCypher::IntegerLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return double
|
||||
*/
|
||||
antlrcpp::Any visitDoubleLiteral(MemgraphCypher::DoubleLiteralContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Delete*
|
||||
*/
|
||||
antlrcpp::Any visitCypherDelete(MemgraphCypher::CypherDeleteContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Where*
|
||||
*/
|
||||
antlrcpp::Any visitWhere(MemgraphCypher::WhereContext *ctx) override;
|
||||
|
||||
/**
|
||||
* return vector<Clause*>
|
||||
*/
|
||||
antlrcpp::Any visitSet(MemgraphCypher::SetContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Clause*
|
||||
*/
|
||||
antlrcpp::Any visitSetItem(MemgraphCypher::SetItemContext *ctx) override;
|
||||
|
||||
/**
|
||||
* return vector<Clause*>
|
||||
*/
|
||||
antlrcpp::Any visitRemove(MemgraphCypher::RemoveContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Clause*
|
||||
*/
|
||||
antlrcpp::Any visitRemoveItem(MemgraphCypher::RemoveItemContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return PropertyLookup*
|
||||
*/
|
||||
antlrcpp::Any visitPropertyExpression(MemgraphCypher::PropertyExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return IfOperator*
|
||||
*/
|
||||
antlrcpp::Any visitCaseExpression(MemgraphCypher::CaseExpressionContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Never call this. Ast generation for this production is done in
|
||||
* @c visitCaseExpression.
|
||||
*/
|
||||
antlrcpp::Any visitCaseAlternatives(MemgraphCypher::CaseAlternativesContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return With*
|
||||
*/
|
||||
antlrcpp::Any visitWith(MemgraphCypher::WithContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Merge*
|
||||
*/
|
||||
antlrcpp::Any visitMerge(MemgraphCypher::MergeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Unwind*
|
||||
*/
|
||||
antlrcpp::Any visitUnwind(MemgraphCypher::UnwindContext *ctx) override;
|
||||
|
||||
/**
|
||||
* Never call this. Ast generation for these expressions should be done by
|
||||
* explicitly visiting the members of @c FilterExpressionContext.
|
||||
*/
|
||||
antlrcpp::Any visitFilterExpression(MemgraphCypher::FilterExpressionContext *) override;
|
||||
|
||||
/**
|
||||
* @return Foreach*
|
||||
*/
|
||||
antlrcpp::Any visitForeach(MemgraphCypher::ForeachContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaPropertyMap(MemgraphCypher::SchemaPropertyMapContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchemas(MemgraphCypher::ShowSchemasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) override;
|
||||
|
||||
public:
|
||||
Query *query() { return query_; }
|
||||
const static std::string kAnonPrefix;
|
||||
|
||||
struct QueryInfo {
|
||||
bool is_cacheable{true};
|
||||
bool has_load_csv{false};
|
||||
};
|
||||
|
||||
const auto &GetQueryInfo() const { return query_info_; }
|
||||
|
||||
private:
|
||||
LabelIx AddLabel(const std::string &name);
|
||||
PropertyIx AddProperty(const std::string &name);
|
||||
EdgeTypeIx AddEdgeType(const std::string &name);
|
||||
|
||||
ParsingContext context_;
|
||||
AstStorage *storage_;
|
||||
|
||||
std::unordered_map<uint8_t, std::variant<Expression *, std::string, std::vector<std::string>,
|
||||
std::unordered_map<Expression *, Expression *>>>
|
||||
memory_;
|
||||
// Set of identifiers from queries.
|
||||
std::unordered_set<std::string> users_identifiers;
|
||||
// Identifiers that user didn't name.
|
||||
std::vector<Identifier **> anonymous_identifiers;
|
||||
Query *query_ = nullptr;
|
||||
// All return items which are not variables must be aliased in with.
|
||||
// We use this variable in visitReturnItem to check if we are in with or
|
||||
// return.
|
||||
bool in_with_ = false;
|
||||
|
||||
QueryInfo query_info_;
|
||||
};
|
||||
} // namespace memgraph::query::v2::frontend
|
||||
311
src/query/v2/frontend/ast/pretty_print.cpp
Normal file
311
src/query/v2/frontend/ast/pretty_print.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
// 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/frontend/ast/pretty_print.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
namespace {
|
||||
|
||||
class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
|
||||
public:
|
||||
explicit ExpressionPrettyPrinter(std::ostream *out);
|
||||
|
||||
// Unary operators
|
||||
void Visit(NotOperator &op) override;
|
||||
void Visit(UnaryPlusOperator &op) override;
|
||||
void Visit(UnaryMinusOperator &op) override;
|
||||
void Visit(IsNullOperator &op) override;
|
||||
|
||||
// Binary operators
|
||||
void Visit(OrOperator &op) override;
|
||||
void Visit(XorOperator &op) override;
|
||||
void Visit(AndOperator &op) override;
|
||||
void Visit(AdditionOperator &op) override;
|
||||
void Visit(SubtractionOperator &op) override;
|
||||
void Visit(MultiplicationOperator &op) override;
|
||||
void Visit(DivisionOperator &op) override;
|
||||
void Visit(ModOperator &op) override;
|
||||
void Visit(NotEqualOperator &op) override;
|
||||
void Visit(EqualOperator &op) override;
|
||||
void Visit(LessOperator &op) override;
|
||||
void Visit(GreaterOperator &op) override;
|
||||
void Visit(LessEqualOperator &op) override;
|
||||
void Visit(GreaterEqualOperator &op) override;
|
||||
void Visit(InListOperator &op) override;
|
||||
void Visit(SubscriptOperator &op) override;
|
||||
|
||||
// Other
|
||||
void Visit(ListSlicingOperator &op) override;
|
||||
void Visit(IfOperator &op) override;
|
||||
void Visit(ListLiteral &op) override;
|
||||
void Visit(MapLiteral &op) override;
|
||||
void Visit(LabelsTest &op) override;
|
||||
void Visit(Aggregation &op) override;
|
||||
void Visit(Function &op) override;
|
||||
void Visit(Reduce &op) override;
|
||||
void Visit(Coalesce &op) override;
|
||||
void Visit(Extract &op) override;
|
||||
void Visit(All &op) override;
|
||||
void Visit(Single &op) override;
|
||||
void Visit(Any &op) override;
|
||||
void Visit(None &op) override;
|
||||
void Visit(Identifier &op) override;
|
||||
void Visit(PrimitiveLiteral &op) override;
|
||||
void Visit(PropertyLookup &op) override;
|
||||
void Visit(ParameterLookup &op) override;
|
||||
void Visit(NamedExpression &op) override;
|
||||
void Visit(RegexMatch &op) override;
|
||||
|
||||
private:
|
||||
std::ostream *out_;
|
||||
};
|
||||
|
||||
// Declare all of the different `PrintObject` overloads upfront since they're
|
||||
// mutually recursive. Without this, overload resolution depends on the ordering
|
||||
// of the overloads within the source, which is quite fragile.
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const T &arg);
|
||||
|
||||
void PrintObject(std::ostream *out, const std::string &str);
|
||||
|
||||
void PrintObject(std::ostream *out, Aggregation::Op op);
|
||||
|
||||
void PrintObject(std::ostream *out, Expression *expr);
|
||||
|
||||
void PrintObject(std::ostream *out, Identifier *expr);
|
||||
|
||||
void PrintObject(std::ostream *out, const storage::v3::PropertyValue &value);
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const std::vector<T> &vec);
|
||||
|
||||
template <typename K, typename V>
|
||||
void PrintObject(std::ostream *out, const std::map<K, V> &map);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void PrintObject(std::ostream *out, const std::string &str) { *out << utils::Escape(str); }
|
||||
|
||||
void PrintObject(std::ostream *out, Aggregation::Op op) { *out << Aggregation::OpToString(op); }
|
||||
|
||||
void PrintObject(std::ostream *out, Expression *expr) {
|
||||
if (expr) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
} else {
|
||||
*out << "<null>";
|
||||
}
|
||||
}
|
||||
|
||||
void PrintObject(std::ostream *out, Identifier *expr) { PrintObject(out, static_cast<Expression *>(expr)); }
|
||||
|
||||
void PrintObject(std::ostream *out, const storage::v3::PropertyValue &value) {
|
||||
switch (value.type()) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
*out << "null";
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::String:
|
||||
PrintObject(out, value.ValueString());
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::Bool:
|
||||
*out << (value.ValueBool() ? "true" : "false");
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::Int:
|
||||
PrintObject(out, value.ValueInt());
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::Double:
|
||||
PrintObject(out, value.ValueDouble());
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::List:
|
||||
PrintObject(out, value.ValueList());
|
||||
break;
|
||||
|
||||
case storage::v3::PropertyValue::Type::Map:
|
||||
PrintObject(out, value.ValueMap());
|
||||
break;
|
||||
case storage::v3::PropertyValue::Type::TemporalData:
|
||||
PrintObject(out, value.ValueTemporalData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintObject(std::ostream *out, const std::vector<T> &vec) {
|
||||
*out << "[";
|
||||
utils::PrintIterable(*out, vec, ", ", [](auto &stream, const auto &item) { PrintObject(&stream, item); });
|
||||
*out << "]";
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void PrintObject(std::ostream *out, const std::map<K, V> &map) {
|
||||
*out << "{";
|
||||
utils::PrintIterable(*out, map, ", ", [](auto &stream, const auto &item) {
|
||||
PrintObject(&stream, item.first);
|
||||
stream << ": ";
|
||||
PrintObject(&stream, item.second);
|
||||
});
|
||||
*out << "}";
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrintOperatorArgs(std::ostream *out, const T &arg) {
|
||||
*out << " ";
|
||||
PrintObject(out, arg);
|
||||
*out << ")";
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
void PrintOperatorArgs(std::ostream *out, const T &arg, const Ts &...args) {
|
||||
*out << " ";
|
||||
PrintObject(out, arg);
|
||||
PrintOperatorArgs(out, args...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void PrintOperator(std::ostream *out, const std::string &name, const Ts &...args) {
|
||||
*out << "(" << name;
|
||||
PrintOperatorArgs(out, args...);
|
||||
}
|
||||
|
||||
ExpressionPrettyPrinter::ExpressionPrettyPrinter(std::ostream *out) : out_(out) {}
|
||||
|
||||
#define UNARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
||||
void ExpressionPrettyPrinter::Visit(OP_NODE &op) { PrintOperator(out_, OP_STR, op.expression_); }
|
||||
|
||||
UNARY_OPERATOR_VISIT(NotOperator, "Not");
|
||||
UNARY_OPERATOR_VISIT(UnaryPlusOperator, "+");
|
||||
UNARY_OPERATOR_VISIT(UnaryMinusOperator, "-");
|
||||
UNARY_OPERATOR_VISIT(IsNullOperator, "IsNull");
|
||||
|
||||
#undef UNARY_OPERATOR_VISIT
|
||||
|
||||
#define BINARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
||||
void ExpressionPrettyPrinter::Visit(OP_NODE &op) { PrintOperator(out_, OP_STR, op.expression1_, op.expression2_); }
|
||||
|
||||
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(InListOperator, "In");
|
||||
BINARY_OPERATOR_VISIT(SubscriptOperator, "Subscript");
|
||||
|
||||
#undef BINARY_OPERATOR_VISIT
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(ListSlicingOperator &op) {
|
||||
PrintOperator(out_, "ListSlicing", op.list_, op.lower_bound_, op.upper_bound_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(IfOperator &op) {
|
||||
PrintOperator(out_, "If", op.condition_, op.then_expression_, op.else_expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(ListLiteral &op) { PrintOperator(out_, "ListLiteral", op.elements_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(MapLiteral &op) {
|
||||
std::map<std::string, Expression *> map;
|
||||
for (const auto &kv : op.elements_) {
|
||||
map[kv.first.name] = kv.second;
|
||||
}
|
||||
PrintObject(out_, map);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(LabelsTest &op) { PrintOperator(out_, "LabelsTest", op.expression_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Aggregation &op) { PrintOperator(out_, "Aggregation", op.op_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Function &op) { PrintOperator(out_, "Function", op.function_name_, op.arguments_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Reduce &op) {
|
||||
PrintOperator(out_, "Reduce", op.accumulator_, op.initializer_, op.identifier_, op.list_, op.expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Coalesce &op) { PrintOperator(out_, "Coalesce", op.expressions_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Extract &op) {
|
||||
PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(All &op) {
|
||||
PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Single &op) {
|
||||
PrintOperator(out_, "Single", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Any &op) {
|
||||
PrintOperator(out_, "Any", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(None &op) {
|
||||
PrintOperator(out_, "None", op.identifier_, op.list_expression_, op.where_->expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(Identifier &op) { PrintOperator(out_, "Identifier", op.name_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(PrimitiveLiteral &op) { PrintObject(out_, op.value_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(PropertyLookup &op) {
|
||||
PrintOperator(out_, "PropertyLookup", op.expression_, op.property_.name);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(ParameterLookup &op) { PrintOperator(out_, "ParameterLookup", op.token_position_); }
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(NamedExpression &op) {
|
||||
PrintOperator(out_, "NamedExpression", op.name_, op.expression_);
|
||||
}
|
||||
|
||||
void ExpressionPrettyPrinter::Visit(RegexMatch &op) { PrintOperator(out_, "=~", op.string_expr_, op.regex_); }
|
||||
|
||||
} // namespace
|
||||
|
||||
void PrintExpression(Expression *expr, std::ostream *out) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
}
|
||||
|
||||
void PrintExpression(NamedExpression *expr, std::ostream *out) {
|
||||
ExpressionPrettyPrinter printer{out};
|
||||
expr->Accept(printer);
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -11,9 +11,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "query/v2/bindings/bindings.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#include "expr/ast/pretty_print.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
void PrintExpression(Expression *expr, std::ostream *out);
|
||||
void PrintExpression(NamedExpression *expr, std::ostream *out);
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* When changing this grammar make sure to update constants in
|
||||
* src/parser/stripped_lexer_constants.hpp (kKeywords, kSpecialTokens
|
||||
* src/query/frontend/stripped_lexer_constants.hpp (kKeywords, kSpecialTokens
|
||||
* and bitsets) if needed.
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
/*
|
||||
* When changing this grammar make sure to update constants in
|
||||
* src/parser/stripped_lexer_constants.hpp (kKeywords, kSpecialTokens
|
||||
* src/query/frontend/stripped_lexer_constants.hpp (kKeywords, kSpecialTokens
|
||||
* and bitsets) if needed.
|
||||
*/
|
||||
|
||||
@@ -14,29 +14,17 @@
|
||||
#include <string>
|
||||
|
||||
#include "antlr4-runtime.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "parser/opencypher/generated/MemgraphCypher.h"
|
||||
#include "parser/opencypher/generated/MemgraphCypherLexer.h"
|
||||
#include "utils/concepts.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypher.h"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypherLexer.h"
|
||||
|
||||
namespace memgraph::frontend::opencypher {
|
||||
|
||||
class SyntaxException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
SyntaxException() : SyntaxException("") {}
|
||||
};
|
||||
namespace memgraph::query::v2::frontend::opencypher {
|
||||
|
||||
/**
|
||||
* Generates openCypher AST
|
||||
* This thing must me a class since parser.cypher() returns pointer and there is
|
||||
* no way for us to get ownership over the object.
|
||||
*/
|
||||
enum class ParserOpTag : uint8_t {
|
||||
CYPHER, EXPRESSION
|
||||
};
|
||||
|
||||
template<ParserOpTag Tag = ParserOpTag::CYPHER>
|
||||
class Parser {
|
||||
public:
|
||||
/**
|
||||
@@ -46,14 +34,9 @@ class Parser {
|
||||
Parser(const std::string query) : query_(std::move(query)) {
|
||||
parser_.removeErrorListeners();
|
||||
parser_.addErrorListener(&error_listener_);
|
||||
if constexpr(Tag == ParserOpTag::CYPHER) {
|
||||
tree_ = parser_.cypher();
|
||||
}
|
||||
else {
|
||||
tree_ = parser_.expression();
|
||||
}
|
||||
tree_ = parser_.cypher();
|
||||
if (parser_.getNumberOfSyntaxErrors()) {
|
||||
throw SyntaxException(error_listener_.error_);
|
||||
throw query::v2::SyntaxException(error_listener_.error_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +65,4 @@ class Parser {
|
||||
antlropencypher::MemgraphCypher parser_{&tokens_};
|
||||
antlr4::tree::ParseTree *tree_ = nullptr;
|
||||
};
|
||||
} // namespace memgraph::frontend::opencypher
|
||||
} // namespace memgraph::query::v2::frontend::opencypher
|
||||
@@ -9,18 +9,18 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "expr/parsing.hpp"
|
||||
#include "query/v2/frontend/parsing.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "expr/exceptions.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace memgraph::query::v2::frontend {
|
||||
|
||||
int64_t ParseIntegerLiteral(const std::string &s) {
|
||||
try {
|
||||
@@ -181,4 +181,4 @@ std::string ParseParameter(const std::string &s) {
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace memgraph::expr
|
||||
} // namespace memgraph::query::v2::frontend
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace memgraph::query::v2::frontend {
|
||||
|
||||
// These are the functions for parsing literals and parameter names from
|
||||
// opencypher query.
|
||||
@@ -24,4 +24,4 @@ std::string ParseStringLiteral(const std::string &s);
|
||||
double ParseDoubleLiteral(const std::string &s);
|
||||
std::string ParseParameter(const std::string &s);
|
||||
|
||||
} // namespace memgraph::expr
|
||||
} // namespace memgraph::query::v2::frontend
|
||||
@@ -9,8 +9,9 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/v2/bindings/ast_visitor.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/v2/procedure/module.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
@@ -85,7 +86,12 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(CallProcedure & /*procedure*/) override {
|
||||
bool PreVisit(CallProcedure &procedure) override {
|
||||
const auto maybe_proc =
|
||||
procedure::FindProcedure(procedure::gModuleRegistry, procedure.procedure_name_, utils::NewDeleteResource());
|
||||
if (maybe_proc && maybe_proc->second->info.required_privilege) {
|
||||
AddPrivilege(*maybe_proc->second->info.required_privilege);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(Delete & /*unused*/) override {
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
cpp<#
|
||||
|
||||
(lcp:namespace memgraph)
|
||||
(lcp:namespace expr)
|
||||
(lcp:namespace query)
|
||||
(lcp:namespace v2)
|
||||
|
||||
(lcp:define-class symbol ()
|
||||
((name "std::string" :scope :public)
|
||||
@@ -65,15 +66,16 @@ cpp<#
|
||||
cpp<#)
|
||||
(:serialize (:slk)))
|
||||
|
||||
(lcp:pop-namespace) ;; expr
|
||||
(lcp:pop-namespace) ;; v2
|
||||
(lcp:pop-namespace) ;; query
|
||||
(lcp:pop-namespace) ;; memgraph
|
||||
|
||||
#>cpp
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::expr::Symbol> {
|
||||
size_t operator()(const memgraph::expr::Symbol &symbol) const {
|
||||
struct hash<memgraph::query::v2::Symbol> {
|
||||
size_t operator()(const memgraph::query::v2::Symbol &symbol) const {
|
||||
size_t prime = 265443599u;
|
||||
size_t hash = std::hash<int>{}(symbol.position());
|
||||
hash ^= prime * std::hash<std::string>{}(symbol.name());
|
||||
625
src/query/v2/frontend/semantic/symbol_generator.cpp
Normal file
625
src/query/v2/frontend/semantic/symbol_generator.cpp
Normal file
@@ -0,0 +1,625 @@
|
||||
// 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.
|
||||
|
||||
// Copyright 2017 Memgraph
|
||||
//
|
||||
// Created by Teon Banek on 24-03-2017
|
||||
|
||||
#include "query/v2/frontend/semantic/symbol_generator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/ast_visitor.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
namespace {
|
||||
std::unordered_map<std::string, Identifier *> GeneratePredefinedIdentifierMap(
|
||||
const std::vector<Identifier *> &predefined_identifiers) {
|
||||
std::unordered_map<std::string, Identifier *> identifier_map;
|
||||
for (const auto &identifier : predefined_identifiers) {
|
||||
identifier_map.emplace(identifier->name_, identifier);
|
||||
}
|
||||
|
||||
return identifier_map;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SymbolGenerator::SymbolGenerator(SymbolTable *symbol_table, const std::vector<Identifier *> &predefined_identifiers)
|
||||
: symbol_table_(symbol_table),
|
||||
predefined_identifiers_{GeneratePredefinedIdentifierMap(predefined_identifiers)},
|
||||
scopes_(1, Scope()) {}
|
||||
|
||||
std::optional<Symbol> SymbolGenerator::FindSymbolInScope(const std::string &name, const Scope &scope,
|
||||
Symbol::Type type) {
|
||||
if (auto it = scope.symbols.find(name); it != scope.symbols.end()) {
|
||||
const auto &symbol = it->second;
|
||||
// Unless we have `ANY` type, check that types match.
|
||||
if (type != Symbol::Type::ANY && symbol.type() != Symbol::Type::ANY && type != symbol.type()) {
|
||||
throw TypeMismatchError(name, Symbol::TypeToString(symbol.type()), Symbol::TypeToString(type));
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto SymbolGenerator::CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type, int token_position) {
|
||||
auto symbol = symbol_table_->CreateSymbol(name, user_declared, type, token_position);
|
||||
scopes_.back().symbols[name] = symbol;
|
||||
return symbol;
|
||||
}
|
||||
|
||||
auto SymbolGenerator::GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type) {
|
||||
auto &scope = scopes_.back();
|
||||
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
|
||||
return *maybe_symbol;
|
||||
}
|
||||
return CreateSymbol(name, user_declared, type);
|
||||
}
|
||||
|
||||
auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) {
|
||||
// NOLINTNEXTLINE
|
||||
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
|
||||
if (auto maybe_symbol = FindSymbolInScope(name, *scope, type); maybe_symbol) {
|
||||
return *maybe_symbol;
|
||||
}
|
||||
}
|
||||
return CreateSymbol(name, user_declared, type);
|
||||
}
|
||||
|
||||
void SymbolGenerator::VisitReturnBody(ReturnBody &body, Where *where) {
|
||||
auto &scope = scopes_.back();
|
||||
for (auto &expr : body.named_expressions) {
|
||||
expr->Accept(*this);
|
||||
}
|
||||
std::vector<Symbol> user_symbols;
|
||||
if (body.all_identifiers) {
|
||||
// Carry over user symbols because '*' appeared.
|
||||
for (const auto &sym_pair : scope.symbols) {
|
||||
if (!sym_pair.second.user_declared()) {
|
||||
continue;
|
||||
}
|
||||
user_symbols.emplace_back(sym_pair.second);
|
||||
}
|
||||
if (user_symbols.empty()) {
|
||||
throw SemanticException("There are no variables in scope to use for '*'.");
|
||||
}
|
||||
}
|
||||
// WITH/RETURN clause removes declarations of all the previous variables and
|
||||
// declares only those established through named expressions. New declarations
|
||||
// must not be visible inside named expressions themselves.
|
||||
bool removed_old_names = false;
|
||||
if ((!where && body.order_by.empty()) || scope.has_aggregation) {
|
||||
// WHERE and ORDER BY need to see both the old and new symbols, unless we
|
||||
// have an aggregation. Therefore, we can clear the symbols immediately if
|
||||
// there is neither ORDER BY nor WHERE, or we have an aggregation.
|
||||
scope.symbols.clear();
|
||||
removed_old_names = true;
|
||||
}
|
||||
// Create symbols for named expressions.
|
||||
std::unordered_set<std::string> new_names;
|
||||
for (const auto &user_sym : user_symbols) {
|
||||
new_names.insert(user_sym.name());
|
||||
scope.symbols[user_sym.name()] = user_sym;
|
||||
}
|
||||
for (auto &named_expr : body.named_expressions) {
|
||||
const auto &name = named_expr->name_;
|
||||
if (!new_names.insert(name).second) {
|
||||
throw SemanticException("Multiple results with the same name '{}' are not allowed.", name);
|
||||
}
|
||||
// An improvement would be to infer the type of the expression, so that the
|
||||
// new symbol would have a more specific type.
|
||||
named_expr->MapTo(CreateSymbol(name, true, Symbol::Type::ANY, named_expr->token_position_));
|
||||
}
|
||||
scope.in_order_by = true;
|
||||
for (const auto &order_pair : body.order_by) {
|
||||
order_pair.expression->Accept(*this);
|
||||
}
|
||||
scope.in_order_by = false;
|
||||
if (body.skip) {
|
||||
scope.in_skip = true;
|
||||
body.skip->Accept(*this);
|
||||
scope.in_skip = false;
|
||||
}
|
||||
if (body.limit) {
|
||||
scope.in_limit = true;
|
||||
body.limit->Accept(*this);
|
||||
scope.in_limit = false;
|
||||
}
|
||||
if (where) where->Accept(*this);
|
||||
if (!removed_old_names) {
|
||||
// We have an ORDER BY or WHERE, but no aggregation, which means we didn't
|
||||
// clear the old symbols, so do it now. We cannot just call clear, because
|
||||
// we've added new symbols.
|
||||
for (auto sym_it = scope.symbols.begin(); sym_it != scope.symbols.end();) {
|
||||
if (new_names.find(sym_it->first) == new_names.end()) {
|
||||
sym_it = scope.symbols.erase(sym_it);
|
||||
} else {
|
||||
sym_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
scopes_.back().has_aggregation = false;
|
||||
}
|
||||
|
||||
// Query
|
||||
|
||||
bool SymbolGenerator::PreVisit(SingleQuery &) {
|
||||
prev_return_names_ = curr_return_names_;
|
||||
curr_return_names_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Union
|
||||
|
||||
bool SymbolGenerator::PreVisit(CypherUnion &) {
|
||||
scopes_.back() = Scope();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(CypherUnion &cypher_union) {
|
||||
if (prev_return_names_ != curr_return_names_) {
|
||||
throw SemanticException("All subqueries in an UNION must have the same column names.");
|
||||
}
|
||||
|
||||
// create new symbols for the result of the union
|
||||
for (const auto &name : curr_return_names_) {
|
||||
auto symbol = CreateSymbol(name, false);
|
||||
cypher_union.union_symbols_.push_back(symbol);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clauses
|
||||
|
||||
bool SymbolGenerator::PreVisit(Create &) {
|
||||
scopes_.back().in_create = true;
|
||||
return true;
|
||||
}
|
||||
bool SymbolGenerator::PostVisit(Create &) {
|
||||
scopes_.back().in_create = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(CallProcedure &call_proc) {
|
||||
for (auto *expr : call_proc.arguments_) {
|
||||
expr->Accept(*this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
|
||||
for (auto *ident : call_proc.result_identifiers_) {
|
||||
if (HasSymbolLocalScope(ident->name_)) {
|
||||
throw RedeclareVariableError(ident->name_);
|
||||
}
|
||||
ident->MapTo(CreateSymbol(ident->name_, true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(LoadCsv &load_csv) { return false; }
|
||||
|
||||
bool SymbolGenerator::PostVisit(LoadCsv &load_csv) {
|
||||
if (HasSymbolLocalScope(load_csv.row_var_->name_)) {
|
||||
throw RedeclareVariableError(load_csv.row_var_->name_);
|
||||
}
|
||||
load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Return &ret) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_return = true;
|
||||
VisitReturnBody(ret.body_);
|
||||
scope.in_return = false;
|
||||
return false; // We handled the traversal ourselves.
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(Return &) {
|
||||
for (const auto &name_symbol : scopes_.back().symbols) curr_return_names_.insert(name_symbol.first);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(With &with) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_with = true;
|
||||
VisitReturnBody(with.body_, with.where_);
|
||||
scope.in_with = false;
|
||||
return false; // We handled the traversal ourselves.
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Where &) {
|
||||
scopes_.back().in_where = true;
|
||||
return true;
|
||||
}
|
||||
bool SymbolGenerator::PostVisit(Where &) {
|
||||
scopes_.back().in_where = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Merge &) {
|
||||
scopes_.back().in_merge = true;
|
||||
return true;
|
||||
}
|
||||
bool SymbolGenerator::PostVisit(Merge &) {
|
||||
scopes_.back().in_merge = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(Unwind &unwind) {
|
||||
const auto &name = unwind.named_expression_->name_;
|
||||
if (HasSymbolLocalScope(name)) {
|
||||
throw RedeclareVariableError(name);
|
||||
}
|
||||
unwind.named_expression_->MapTo(CreateSymbol(name, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Match &) {
|
||||
scopes_.back().in_match = true;
|
||||
return true;
|
||||
}
|
||||
bool SymbolGenerator::PostVisit(Match &) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_match = false;
|
||||
// Check variables in property maps after visiting Match, so that they can
|
||||
// reference symbols out of bind order.
|
||||
for (auto &ident : scope.identifiers_in_match) {
|
||||
if (!HasSymbolLocalScope(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
|
||||
throw UnboundVariableError(ident->name_);
|
||||
ident->MapTo(scope.symbols[ident->name_]);
|
||||
}
|
||||
scope.identifiers_in_match.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Foreach &for_each) {
|
||||
const auto &name = for_each.named_expression_->name_;
|
||||
scopes_.emplace_back(Scope());
|
||||
scopes_.back().in_foreach = true;
|
||||
for_each.named_expression_->MapTo(
|
||||
CreateSymbol(name, true, Symbol::Type::ANY, for_each.named_expression_->token_position_));
|
||||
return true;
|
||||
}
|
||||
bool SymbolGenerator::PostVisit([[maybe_unused]] Foreach &for_each) {
|
||||
scopes_.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expressions
|
||||
|
||||
SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
auto &scope = scopes_.back();
|
||||
if (scope.in_skip || scope.in_limit) {
|
||||
throw SemanticException("Variables are not allowed in {}.", scope.in_skip ? "SKIP" : "LIMIT");
|
||||
}
|
||||
Symbol symbol;
|
||||
if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) {
|
||||
// If we are in the pattern, and outside of a node or an edge, the
|
||||
// identifier is the pattern name.
|
||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH);
|
||||
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
|
||||
// Patterns used to create nodes and edges cannot redeclare already
|
||||
// established bindings. Declaration only happens in single node
|
||||
// patterns and in edge patterns. OpenCypher example,
|
||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
||||
// since `n` now references the bound node instead of declaring it.
|
||||
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
auto type = Symbol::Type::VERTEX;
|
||||
if (scope.visiting_edge) {
|
||||
// Edge referencing is not allowed (like in Neo4j):
|
||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
|
||||
if (HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
|
||||
}
|
||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, type);
|
||||
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
|
||||
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) {
|
||||
// Prevent variable path bounds to reference the identifier which is bound
|
||||
// by the variable path itself.
|
||||
throw UnboundVariableError(ident.name_);
|
||||
}
|
||||
// Variables in property maps or bounds of variable length path during MATCH
|
||||
// can reference symbols bound later in the same MATCH. We collect them
|
||||
// here, so that they can be checked after visiting Match.
|
||||
scope.identifiers_in_match.emplace_back(&ident);
|
||||
} else {
|
||||
// Everything else references a bound symbol.
|
||||
if (!HasSymbol(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) throw UnboundVariableError(ident.name_);
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::ANY);
|
||||
}
|
||||
ident.MapTo(symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Aggregation &aggr) {
|
||||
auto &scope = scopes_.back();
|
||||
// Check if the aggregation can be used in this context. This check should
|
||||
// probably move to a separate phase, which checks if the query is well
|
||||
// formed.
|
||||
if ((!scope.in_return && !scope.in_with) || scope.in_order_by || scope.in_skip || scope.in_limit || scope.in_where) {
|
||||
throw SemanticException("Aggregation functions are only allowed in WITH and RETURN.");
|
||||
}
|
||||
if (scope.in_aggregation) {
|
||||
throw SemanticException(
|
||||
"Using aggregation functions inside aggregation functions is not "
|
||||
"allowed.");
|
||||
}
|
||||
if (scope.num_if_operators) {
|
||||
// Neo allows aggregations here and produces very interesting behaviors.
|
||||
// To simplify implementation at this moment we decided to completely
|
||||
// disallow aggregations inside of the CASE.
|
||||
// However, in some cases aggregation makes perfect sense, for example:
|
||||
// CASE count(n) WHEN 10 THEN "YES" ELSE "NO" END.
|
||||
// TODO: Rethink of allowing aggregations in some parts of the CASE
|
||||
// construct.
|
||||
throw SemanticException("Using aggregation functions inside of CASE is not allowed.");
|
||||
}
|
||||
// Create a virtual symbol for aggregation result.
|
||||
// Currently, we only have aggregation operators which return numbers.
|
||||
auto aggr_name = Aggregation::OpToString(aggr.op_) + std::to_string(aggr.symbol_pos_);
|
||||
aggr.MapTo(CreateSymbol(aggr_name, false, Symbol::Type::NUMBER));
|
||||
scope.in_aggregation = true;
|
||||
scope.has_aggregation = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(Aggregation &) {
|
||||
scopes_.back().in_aggregation = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(IfOperator &) {
|
||||
++scopes_.back().num_if_operators;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(IfOperator &) {
|
||||
--scopes_.back().num_if_operators;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(All &all) {
|
||||
all.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(all.where_->expression_, {all.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Single &single) {
|
||||
single.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(single.where_->expression_, {single.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Any &any) {
|
||||
any.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(any.where_->expression_, {any.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(None &none) {
|
||||
none.list_expression_->Accept(*this);
|
||||
VisitWithIdentifiers(none.where_->expression_, {none.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Reduce &reduce) {
|
||||
reduce.initializer_->Accept(*this);
|
||||
reduce.list_->Accept(*this);
|
||||
VisitWithIdentifiers(reduce.expression_, {reduce.accumulator_, reduce.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Extract &extract) {
|
||||
extract.list_->Accept(*this);
|
||||
VisitWithIdentifiers(extract.expression_, {extract.identifier_});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pattern and its subparts.
|
||||
|
||||
bool SymbolGenerator::PreVisit(Pattern &pattern) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_pattern = true;
|
||||
if ((scope.in_create || scope.in_merge) && pattern.atoms_.size() == 1U) {
|
||||
MG_ASSERT(utils::IsSubtype(*pattern.atoms_[0], NodeAtom::kType), "Expected a single NodeAtom in Pattern");
|
||||
scope.in_create_node = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(Pattern &) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.in_pattern = false;
|
||||
scope.in_create_node = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(NodeAtom &node_atom) {
|
||||
auto &scope = scopes_.back();
|
||||
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
|
||||
const auto &node_name = node_atom.identifier_->name_;
|
||||
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) {
|
||||
throw SemanticException("Cannot create node '" + node_name +
|
||||
"' with labels or properties, because it is already declared.");
|
||||
}
|
||||
scope.in_pattern_atom_identifier = true;
|
||||
node_atom.identifier_->Accept(*this);
|
||||
scope.in_pattern_atom_identifier = false;
|
||||
};
|
||||
|
||||
scope.in_node_atom = true;
|
||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&node_atom.properties_)) {
|
||||
bool props_or_labels = !properties->empty() || !node_atom.labels_.empty();
|
||||
|
||||
check_node_semantic(props_or_labels);
|
||||
for (auto kv : *properties) {
|
||||
kv.second->Accept(*this);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
auto &properties_parameter = std::get<ParameterLookup *>(node_atom.properties_);
|
||||
bool props_or_labels = !properties_parameter || !node_atom.labels_.empty();
|
||||
|
||||
check_node_semantic(props_or_labels);
|
||||
properties_parameter->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(NodeAtom &) {
|
||||
scopes_.back().in_node_atom = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.visiting_edge = &edge_atom;
|
||||
if (scope.in_create || scope.in_merge) {
|
||||
scope.in_create_edge = true;
|
||||
if (edge_atom.edge_types_.size() != 1U) {
|
||||
throw SemanticException(
|
||||
"A single relationship type must be specified "
|
||||
"when creating an edge.");
|
||||
}
|
||||
if (scope.in_create && // Merge allows bidirectionality
|
||||
edge_atom.direction_ == EdgeAtom::Direction::BOTH) {
|
||||
throw SemanticException(
|
||||
"Bidirectional relationship are not supported "
|
||||
"when creating an edge");
|
||||
}
|
||||
if (edge_atom.IsVariable()) {
|
||||
throw SemanticException(
|
||||
"Variable length relationships are not supported when creating an "
|
||||
"edge.");
|
||||
}
|
||||
}
|
||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&edge_atom.properties_)) {
|
||||
for (auto kv : *properties) {
|
||||
kv.second->Accept(*this);
|
||||
}
|
||||
} else {
|
||||
std::get<ParameterLookup *>(edge_atom.properties_)->Accept(*this);
|
||||
}
|
||||
if (edge_atom.IsVariable()) {
|
||||
scope.in_edge_range = true;
|
||||
if (edge_atom.lower_bound_) {
|
||||
edge_atom.lower_bound_->Accept(*this);
|
||||
}
|
||||
if (edge_atom.upper_bound_) {
|
||||
edge_atom.upper_bound_->Accept(*this);
|
||||
}
|
||||
scope.in_edge_range = false;
|
||||
scope.in_pattern = false;
|
||||
if (edge_atom.filter_lambda_.expression) {
|
||||
VisitWithIdentifiers(edge_atom.filter_lambda_.expression,
|
||||
{edge_atom.filter_lambda_.inner_edge, edge_atom.filter_lambda_.inner_node});
|
||||
} else {
|
||||
// Create inner symbols, but don't bind them in scope, since they are to
|
||||
// be used in the missing filter expression.
|
||||
auto *inner_edge = edge_atom.filter_lambda_.inner_edge;
|
||||
inner_edge->MapTo(symbol_table_->CreateSymbol(inner_edge->name_, inner_edge->user_declared_, Symbol::Type::EDGE));
|
||||
auto *inner_node = edge_atom.filter_lambda_.inner_node;
|
||||
inner_node->MapTo(
|
||||
symbol_table_->CreateSymbol(inner_node->name_, inner_node->user_declared_, Symbol::Type::VERTEX));
|
||||
}
|
||||
if (edge_atom.weight_lambda_.expression) {
|
||||
VisitWithIdentifiers(edge_atom.weight_lambda_.expression,
|
||||
{edge_atom.weight_lambda_.inner_edge, edge_atom.weight_lambda_.inner_node});
|
||||
}
|
||||
scope.in_pattern = true;
|
||||
}
|
||||
scope.in_pattern_atom_identifier = true;
|
||||
edge_atom.identifier_->Accept(*this);
|
||||
scope.in_pattern_atom_identifier = false;
|
||||
if (edge_atom.total_weight_) {
|
||||
if (HasSymbolLocalScope(edge_atom.total_weight_->name_)) {
|
||||
throw RedeclareVariableError(edge_atom.total_weight_->name_);
|
||||
}
|
||||
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope(
|
||||
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(EdgeAtom &) {
|
||||
auto &scope = scopes_.back();
|
||||
scope.visiting_edge = nullptr;
|
||||
scope.in_create_edge = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SymbolGenerator::VisitWithIdentifiers(Expression *expr, const std::vector<Identifier *> &identifiers) {
|
||||
auto &scope = scopes_.back();
|
||||
std::vector<std::pair<std::optional<Symbol>, Identifier *>> prev_symbols;
|
||||
// Collect previous symbols if they exist.
|
||||
for (const auto &identifier : identifiers) {
|
||||
std::optional<Symbol> prev_symbol;
|
||||
auto prev_symbol_it = scope.symbols.find(identifier->name_);
|
||||
if (prev_symbol_it != scope.symbols.end()) {
|
||||
prev_symbol = prev_symbol_it->second;
|
||||
}
|
||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
||||
prev_symbols.emplace_back(prev_symbol, identifier);
|
||||
}
|
||||
// Visit the expression with the new symbols bound.
|
||||
expr->Accept(*this);
|
||||
// Restore back to previous symbols.
|
||||
for (const auto &prev : prev_symbols) {
|
||||
const auto &prev_symbol = prev.first;
|
||||
const auto &identifier = prev.second;
|
||||
if (prev_symbol) {
|
||||
scope.symbols[identifier->name_] = *prev_symbol;
|
||||
} else {
|
||||
scope.symbols.erase(identifier->name_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SymbolGenerator::HasSymbol(const std::string &name) const {
|
||||
return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); });
|
||||
}
|
||||
|
||||
bool SymbolGenerator::HasSymbolLocalScope(const std::string &name) const {
|
||||
return scopes_.back().symbols.contains(name);
|
||||
}
|
||||
|
||||
bool SymbolGenerator::ConsumePredefinedIdentifier(const std::string &name) {
|
||||
auto it = predefined_identifiers_.find(name);
|
||||
|
||||
if (it == predefined_identifiers_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we can only use the predefined identifier in a single scope so we remove it after creating
|
||||
// a symbol for it
|
||||
auto &identifier = it->second;
|
||||
MG_ASSERT(!identifier->user_declared_, "Predefined symbols cannot be user declared!");
|
||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
||||
predefined_identifiers_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
176
src/query/v2/frontend/semantic/symbol_generator.hpp
Normal file
176
src/query/v2/frontend/semantic/symbol_generator.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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.
|
||||
|
||||
// Copyright 2017 Memgraph
|
||||
//
|
||||
// Created by Teon Banek on 11-03-2017
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
/// Visits the AST and generates symbols for variables.
|
||||
///
|
||||
/// During the process of symbol generation, simple semantic checks are
|
||||
/// performed. Such as, redeclaring a variable or conflicting expectations of
|
||||
/// variable types.
|
||||
class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
public:
|
||||
explicit SymbolGenerator(SymbolTable *symbol_table, const std::vector<Identifier *> &predefined_identifiers);
|
||||
|
||||
using HierarchicalTreeVisitor::PostVisit;
|
||||
using HierarchicalTreeVisitor::PreVisit;
|
||||
using HierarchicalTreeVisitor::Visit;
|
||||
using typename HierarchicalTreeVisitor::ReturnType;
|
||||
|
||||
// Query
|
||||
bool PreVisit(SingleQuery &) override;
|
||||
|
||||
// Union
|
||||
bool PreVisit(CypherUnion &) override;
|
||||
bool PostVisit(CypherUnion &) override;
|
||||
|
||||
// Clauses
|
||||
bool PreVisit(Create &) override;
|
||||
bool PostVisit(Create &) override;
|
||||
bool PreVisit(CallProcedure &) override;
|
||||
bool PostVisit(CallProcedure &) override;
|
||||
bool PreVisit(LoadCsv &) override;
|
||||
bool PostVisit(LoadCsv &) override;
|
||||
bool PreVisit(Return &) override;
|
||||
bool PostVisit(Return &) override;
|
||||
bool PreVisit(With &) override;
|
||||
bool PreVisit(Where &) override;
|
||||
bool PostVisit(Where &) override;
|
||||
bool PreVisit(Merge &) override;
|
||||
bool PostVisit(Merge &) override;
|
||||
bool PostVisit(Unwind &) override;
|
||||
bool PreVisit(Match &) override;
|
||||
bool PostVisit(Match &) override;
|
||||
bool PreVisit(Foreach &) override;
|
||||
bool PostVisit(Foreach &) override;
|
||||
|
||||
// Expressions
|
||||
ReturnType Visit(Identifier &) override;
|
||||
ReturnType Visit(PrimitiveLiteral &) override { return true; }
|
||||
ReturnType Visit(ParameterLookup &) override { return true; }
|
||||
bool PreVisit(Aggregation &) override;
|
||||
bool PostVisit(Aggregation &) override;
|
||||
bool PreVisit(IfOperator &) override;
|
||||
bool PostVisit(IfOperator &) override;
|
||||
bool PreVisit(All &) override;
|
||||
bool PreVisit(Single &) override;
|
||||
bool PreVisit(Any &) override;
|
||||
bool PreVisit(None &) override;
|
||||
bool PreVisit(Reduce &) override;
|
||||
bool PreVisit(Extract &) override;
|
||||
|
||||
// Pattern and its subparts.
|
||||
bool PreVisit(Pattern &) override;
|
||||
bool PostVisit(Pattern &) override;
|
||||
bool PreVisit(NodeAtom &) override;
|
||||
bool PostVisit(NodeAtom &) override;
|
||||
bool PreVisit(EdgeAtom &) override;
|
||||
bool PostVisit(EdgeAtom &) override;
|
||||
|
||||
private:
|
||||
// Scope stores the state of where we are when visiting the AST and a map of
|
||||
// names to symbols.
|
||||
struct Scope {
|
||||
bool in_pattern{false};
|
||||
bool in_merge{false};
|
||||
bool in_create{false};
|
||||
// in_create_node is true if we are creating or merging *only* a node.
|
||||
// Therefore, it is *not* equivalent to (in_create || in_merge) &&
|
||||
// in_node_atom.
|
||||
bool in_create_node{false};
|
||||
// True if creating an edge;
|
||||
// shortcut for (in_create || in_merge) && visiting_edge.
|
||||
bool in_create_edge{false};
|
||||
bool in_node_atom{false};
|
||||
EdgeAtom *visiting_edge{nullptr};
|
||||
bool in_aggregation{false};
|
||||
bool in_return{false};
|
||||
bool in_with{false};
|
||||
bool in_skip{false};
|
||||
bool in_limit{false};
|
||||
bool in_order_by{false};
|
||||
bool in_where{false};
|
||||
bool in_match{false};
|
||||
bool in_foreach{false};
|
||||
// True when visiting a pattern atom (node or edge) identifier, which can be
|
||||
// reused or created in the pattern itself.
|
||||
bool in_pattern_atom_identifier{false};
|
||||
// True when visiting range bounds of a variable path.
|
||||
bool in_edge_range{false};
|
||||
// True if the return/with contains an aggregation in any named expression.
|
||||
bool has_aggregation{false};
|
||||
// Map from variable names to symbols.
|
||||
std::map<std::string, Symbol> symbols;
|
||||
// Identifiers found in property maps of patterns or as variable length path
|
||||
// bounds in a single Match clause. They need to be checked after visiting
|
||||
// Match. Identifiers created by naming vertices, edges and paths are *not*
|
||||
// stored in here.
|
||||
std::vector<Identifier *> identifiers_in_match;
|
||||
// Number of nested IfOperators.
|
||||
int num_if_operators{0};
|
||||
};
|
||||
|
||||
static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope, Symbol::Type type);
|
||||
|
||||
bool HasSymbol(const std::string &name) const;
|
||||
bool HasSymbolLocalScope(const std::string &name) const;
|
||||
|
||||
// @return true if it added a predefined identifier with that name
|
||||
bool ConsumePredefinedIdentifier(const std::string &name);
|
||||
|
||||
// Returns a freshly generated symbol. Previous mapping of the same name to a
|
||||
// different symbol is replaced with the new one.
|
||||
auto CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY,
|
||||
int token_position = -1);
|
||||
|
||||
auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
|
||||
// Returns the symbol by name. If the mapping already exists, checks if the
|
||||
// types match. Otherwise, returns a new symbol.
|
||||
auto GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
|
||||
|
||||
void VisitReturnBody(ReturnBody &body, Where *where = nullptr);
|
||||
|
||||
void VisitWithIdentifiers(Expression *, const std::vector<Identifier *> &);
|
||||
|
||||
SymbolTable *symbol_table_;
|
||||
|
||||
// Identifiers which are injected from outside the query. Each identifier
|
||||
// is mapped by its name.
|
||||
std::unordered_map<std::string, Identifier *> predefined_identifiers_;
|
||||
std::vector<Scope> scopes_;
|
||||
std::unordered_set<std::string> prev_return_names_;
|
||||
std::unordered_set<std::string> curr_return_names_;
|
||||
};
|
||||
|
||||
inline SymbolTable MakeSymbolTable(CypherQuery *query, const std::vector<Identifier *> &predefined_identifiers = {}) {
|
||||
SymbolTable symbol_table;
|
||||
SymbolGenerator symbol_generator(&symbol_table, predefined_identifiers);
|
||||
query->single_query_->Accept(symbol_generator);
|
||||
for (auto *cypher_union : query->cypher_unions_) {
|
||||
cypher_union->Accept(symbol_generator);
|
||||
}
|
||||
return symbol_table;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -14,11 +14,11 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "expr/ast.hpp"
|
||||
#include "expr/semantic/symbol.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
class SymbolTable final {
|
||||
public:
|
||||
@@ -61,4 +61,4 @@ class SymbolTable final {
|
||||
std::map<int32_t, Symbol> table_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::expr
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -18,19 +18,19 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "expr/parsing.hpp"
|
||||
#include "parser/opencypher/generated/MemgraphCypher.h"
|
||||
#include "parser/opencypher/generated/MemgraphCypherBaseVisitor.h"
|
||||
#include "parser/opencypher/generated/MemgraphCypherLexer.h"
|
||||
#include "parser/stripped_lexer_constants.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypher.h"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypherBaseVisitor.h"
|
||||
#include "query/v2/frontend/opencypher/generated/MemgraphCypherLexer.h"
|
||||
#include "query/v2/frontend/parsing.hpp"
|
||||
#include "query/v2/frontend/stripped_lexer_constants.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::v2::frontend {
|
||||
|
||||
using namespace parser::lexer_constants; // NOLINT(google-build-using-namespace)
|
||||
using namespace lexer_constants;
|
||||
|
||||
StrippedQuery::StrippedQuery(const std::string &query) : original_(query) {
|
||||
enum class Token {
|
||||
@@ -134,13 +134,13 @@ StrippedQuery::StrippedQuery(const std::string &query) : original_(query) {
|
||||
case Token::SPACE:
|
||||
break;
|
||||
case Token::STRING:
|
||||
replace_stripped(token_index, expr::ParseStringLiteral(token.second), kStrippedStringToken);
|
||||
replace_stripped(token_index, ParseStringLiteral(token.second), kStrippedStringToken);
|
||||
break;
|
||||
case Token::INT:
|
||||
replace_stripped(token_index, expr::ParseIntegerLiteral(token.second), kStrippedIntToken);
|
||||
replace_stripped(token_index, ParseIntegerLiteral(token.second), kStrippedIntToken);
|
||||
break;
|
||||
case Token::REAL:
|
||||
replace_stripped(token_index, expr::ParseDoubleLiteral(token.second), kStrippedDoubleToken);
|
||||
replace_stripped(token_index, ParseDoubleLiteral(token.second), kStrippedDoubleToken);
|
||||
break;
|
||||
case Token::SPECIAL:
|
||||
case Token::ESCAPED_NAME:
|
||||
@@ -148,7 +148,7 @@ StrippedQuery::StrippedQuery(const std::string &query) : original_(query) {
|
||||
token_strings.push_back(token.second);
|
||||
break;
|
||||
case Token::PARAMETER:
|
||||
parameters_[token_index] = expr::ParseParameter(token.second);
|
||||
parameters_[token_index] = ParseParameter(token.second);
|
||||
token_strings.push_back(token.second);
|
||||
break;
|
||||
}
|
||||
@@ -462,13 +462,13 @@ int StrippedQuery::MatchEscapedName(int start) const {
|
||||
int StrippedQuery::MatchUnescapedName(int start) const {
|
||||
auto i = start;
|
||||
auto got = GetFirstUtf8SymbolCodepoint(original_.data() + i);
|
||||
if (got.first >= parser::lexer_constants::kBitsetSize || !kUnescapedNameAllowedStarts[got.first]) {
|
||||
if (got.first >= lexer_constants::kBitsetSize || !kUnescapedNameAllowedStarts[got.first]) {
|
||||
return 0;
|
||||
}
|
||||
i += got.second;
|
||||
while (i < static_cast<int>(original_.size())) {
|
||||
got = GetFirstUtf8SymbolCodepoint(original_.data() + i);
|
||||
if (got.first >= parser::lexer_constants::kBitsetSize || !kUnescapedNameAllowedParts[got.first]) {
|
||||
if (got.first >= lexer_constants::kBitsetSize || !kUnescapedNameAllowedParts[got.first]) {
|
||||
break;
|
||||
}
|
||||
i += got.second;
|
||||
@@ -487,7 +487,7 @@ int StrippedQuery::MatchWhitespaceAndComments(int start) const {
|
||||
while (i < len) {
|
||||
if (state == State::OUT) {
|
||||
auto got = GetFirstUtf8SymbolCodepoint(original_.data() + i);
|
||||
if (got.first < parser::lexer_constants::kBitsetSize && kSpaceParts[got.first]) {
|
||||
if (got.first < lexer_constants::kBitsetSize && kSpaceParts[got.first]) {
|
||||
i += got.second;
|
||||
} else if (i + 1 < len && original_[i] == '/' && original_[i + 1] == '*') {
|
||||
comment_position = i;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace parser {
|
||||
namespace memgraph::query::v2 {
|
||||
namespace lexer_constants {
|
||||
|
||||
namespace trie {
|
||||
@@ -2922,4 +2922,4 @@ const trie::Trie kSpecialTokens = {";",
|
||||
"\xEF\xB9\xA3", // u8"\ufe63"
|
||||
"\xEF\xBC\x8D"}; // u8"\uff0d"
|
||||
} // namespace lexer_constants
|
||||
} // namespace parser
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -20,14 +20,12 @@
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#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/procedure/cypher_types.hpp"
|
||||
//#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
//#include "query/v2/procedure/module.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "query/v2/procedure/mg_procedure_impl.hpp"
|
||||
#include "query/v2/procedure/module.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
@@ -388,7 +386,38 @@ TypedValue Last(const TypedValue *args, int64_t nargs, const FunctionContext &ct
|
||||
return TypedValue(list.back(), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue Properties(const TypedValue * /*args*/, int64_t /*nargs*/, const FunctionContext & /*ctx*/) { return {}; }
|
||||
TypedValue Properties(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex, Edge>>("properties", args, nargs);
|
||||
auto *dba = ctx.db_accessor;
|
||||
auto get_properties = [&](const auto &record_accessor) {
|
||||
TypedValue::TMap properties(ctx.memory);
|
||||
auto maybe_props = record_accessor.Properties(ctx.view);
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw 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 object 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 properties.");
|
||||
}
|
||||
}
|
||||
for (const auto &property : *maybe_props) {
|
||||
properties.emplace(dba->PropertyToName(property.first), property.second);
|
||||
}
|
||||
return TypedValue(std::move(properties));
|
||||
};
|
||||
const auto &value = args[0];
|
||||
if (value.IsNull()) {
|
||||
return TypedValue(ctx.memory);
|
||||
} else if (value.IsVertex()) {
|
||||
return get_properties(value.ValueVertex());
|
||||
} else {
|
||||
return get_properties(value.ValueEdge());
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, List, String, Map, Path>>("size", args, nargs);
|
||||
@@ -404,8 +433,7 @@ TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ct
|
||||
// to do it.
|
||||
return TypedValue(static_cast<int64_t>(value.ValueMap().size()), ctx.memory);
|
||||
} else {
|
||||
// TODO(kostasrim) Fix the dummy return
|
||||
return TypedValue(int64_t(0), ctx.memory);
|
||||
return TypedValue(static_cast<int64_t>(value.ValuePath().edges().size()), ctx.memory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,22 +467,25 @@ TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext &
|
||||
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);
|
||||
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
|
||||
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(out_degree + in_degree), 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);
|
||||
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(in_degree), 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);
|
||||
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(out_degree), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
@@ -520,7 +551,7 @@ TypedValue Type(const TypedValue *args, int64_t nargs, const FunctionContext &ct
|
||||
FType<Or<Null, Edge>>("type", args, nargs);
|
||||
auto *dba = ctx.db_accessor;
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return TypedValue(static_cast<int64_t>(args[0].ValueEdge().EdgeType()), ctx.memory);
|
||||
return TypedValue(dba->EdgeTypeToName(args[0].ValueEdge().EdgeType()), ctx.memory);
|
||||
}
|
||||
|
||||
TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
@@ -560,27 +591,81 @@ 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*/) {
|
||||
TypedValue Keys(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex, Edge>>("keys", args, nargs);
|
||||
return {};
|
||||
auto *dba = ctx.db_accessor;
|
||||
auto get_keys = [&](const auto &record_accessor) {
|
||||
TypedValue::TVector keys(ctx.memory);
|
||||
auto maybe_props = record_accessor.Properties(ctx.view);
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to get keys from a deleted object.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get keys from an object 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 keys.");
|
||||
}
|
||||
}
|
||||
for (const auto &property : *maybe_props) {
|
||||
keys.emplace_back(dba->PropertyToName(property.first));
|
||||
}
|
||||
return TypedValue(std::move(keys));
|
||||
};
|
||||
const auto &value = args[0];
|
||||
if (value.IsNull()) {
|
||||
return TypedValue(ctx.memory);
|
||||
} else if (value.IsVertex()) {
|
||||
return get_keys(value.ValueVertex());
|
||||
} else {
|
||||
return get_keys(value.ValueEdge());
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Labels(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
FType<Or<Null, Vertex>>("labels", args, nargs);
|
||||
auto *dba = ctx.db_accessor;
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
return {};
|
||||
TypedValue::TVector labels(ctx.memory);
|
||||
auto maybe_labels = args[0].ValueVertex().Labels(ctx.view);
|
||||
if (maybe_labels.HasError()) {
|
||||
switch (maybe_labels.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw 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 QueryRuntimeException("Unexpected error when getting labels.");
|
||||
}
|
||||
}
|
||||
for (const auto &label : *maybe_labels) {
|
||||
labels.emplace_back(dba->LabelToName(label));
|
||||
}
|
||||
return TypedValue(std::move(labels));
|
||||
}
|
||||
|
||||
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 {};
|
||||
const auto &vertices = args[0].ValuePath().vertices();
|
||||
TypedValue::TVector values(ctx.memory);
|
||||
values.reserve(vertices.size());
|
||||
for (const auto &v : vertices) values.emplace_back(v);
|
||||
return TypedValue(std::move(values));
|
||||
}
|
||||
|
||||
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 {};
|
||||
const auto &edges = args[0].ValuePath().edges();
|
||||
TypedValue::TVector values(ctx.memory);
|
||||
values.reserve(edges.size());
|
||||
for (const auto &e : edges) values.emplace_back(e);
|
||||
return TypedValue(std::move(values));
|
||||
}
|
||||
|
||||
TypedValue Range(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
|
||||
@@ -790,9 +875,9 @@ TypedValue Id(const TypedValue *args, int64_t nargs, const FunctionContext &ctx)
|
||||
if (arg.IsNull()) {
|
||||
return TypedValue(ctx.memory);
|
||||
} else if (arg.IsVertex()) {
|
||||
return TypedValue(int64_t(arg.ValueVertex().CypherId()), ctx.memory);
|
||||
return TypedValue(arg.ValueVertex().CypherId(), ctx.memory);
|
||||
} else {
|
||||
return TypedValue(int64_t(arg.ValueEdge().CypherId()), ctx.memory);
|
||||
return TypedValue(arg.ValueEdge().CypherId(), ctx.memory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,6 +1178,52 @@ TypedValue Duration(const TypedValue *args, int64_t nargs, const FunctionContext
|
||||
return TypedValue(utils::Duration(duration_parameters), ctx.memory);
|
||||
}
|
||||
|
||||
std::function<TypedValue(const TypedValue *, const int64_t, const FunctionContext &)> UserFunction(
|
||||
const mgp_func &func, const std::string &fully_qualified_name) {
|
||||
return [func, fully_qualified_name](const TypedValue *args, int64_t nargs, const FunctionContext &ctx) -> TypedValue {
|
||||
/// Find function is called to aquire the lock on Module pointer while user-defined function is executed
|
||||
const auto &maybe_found =
|
||||
procedure::FindFunction(procedure::gModuleRegistry, fully_qualified_name, utils::NewDeleteResource());
|
||||
if (!maybe_found) {
|
||||
throw QueryRuntimeException(
|
||||
"Function '{}' has been unloaded. Please check query modules to confirm that function is loaded in Memgraph.",
|
||||
fully_qualified_name);
|
||||
}
|
||||
/// Explicit extraction of module pointer, to clearly state that the lock is aquired.
|
||||
// NOLINTNEXTLINE(clang-diagnostic-unused-variable)
|
||||
const auto &module_ptr = (*maybe_found).first;
|
||||
|
||||
const auto &func_cb = func.cb;
|
||||
mgp_memory memory{ctx.memory};
|
||||
mgp_func_context functx{ctx.db_accessor, ctx.view};
|
||||
auto graph = mgp_graph::NonWritableGraph(*ctx.db_accessor, ctx.view);
|
||||
|
||||
std::vector<TypedValue> args_list;
|
||||
args_list.reserve(nargs);
|
||||
for (std::size_t i = 0; i < nargs; ++i) {
|
||||
args_list.emplace_back(args[i]);
|
||||
}
|
||||
|
||||
auto function_argument_list = mgp_list(ctx.memory);
|
||||
procedure::ConstructArguments(args_list, func, fully_qualified_name, function_argument_list, graph);
|
||||
|
||||
mgp_func_result maybe_res;
|
||||
func_cb(&function_argument_list, &functx, &maybe_res, &memory);
|
||||
if (maybe_res.error_msg) {
|
||||
throw QueryRuntimeException(*maybe_res.error_msg);
|
||||
}
|
||||
|
||||
if (!maybe_res.value) {
|
||||
throw QueryRuntimeException(
|
||||
"Function '{}' didn't set the result nor the error message. Please either set the result by using "
|
||||
"mgp_func_result_set_value or the error by using mgp_func_result_set_error_msg.",
|
||||
fully_qualified_name);
|
||||
}
|
||||
|
||||
return {*(maybe_res.value), ctx.memory};
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx)> NameToFunction(
|
||||
@@ -1162,7 +1293,6 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
|
||||
if (function_name == kStartsWith) return StartsWith;
|
||||
if (function_name == "SUBSTRING") return Substring;
|
||||
if (function_name == "TOLOWER") return ToLower;
|
||||
// TODO(kostasrim) fix function lookup here
|
||||
if (function_name == "TOSTRING") return ToString;
|
||||
if (function_name == "TOUPPER") return ToUpper;
|
||||
if (function_name == "TRIM") return Trim;
|
||||
@@ -1179,6 +1309,14 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
|
||||
if (function_name == "LOCALDATETIME") return LocalDateTime;
|
||||
if (function_name == "DURATION") return Duration;
|
||||
|
||||
const auto &maybe_found =
|
||||
procedure::FindFunction(procedure::gModuleRegistry, function_name, utils::NewDeleteResource());
|
||||
|
||||
if (maybe_found) {
|
||||
const auto *func = (*maybe_found).second;
|
||||
return UserFunction(*func, function_name);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
class DbAccessor;
|
||||
class TypedValue;
|
||||
|
||||
namespace {
|
||||
const char kStartsWith[] = "STARTSWITH";
|
||||
|
||||
35
src/query/v2/interpret/eval.cpp
Normal file
35
src/query/v2/interpret/eval.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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/interpret/eval.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
int64_t EvaluateInt(ExpressionEvaluator *evaluator, Expression *expr, const std::string &what) {
|
||||
TypedValue value = expr->Accept(*evaluator);
|
||||
try {
|
||||
return value.ValueInt();
|
||||
} catch (TypedValueException &e) {
|
||||
throw QueryRuntimeException(what + " must be an int");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<size_t> EvaluateMemoryLimit(ExpressionEvaluator *eval, Expression *memory_limit, size_t memory_scale) {
|
||||
if (!memory_limit) return std::nullopt;
|
||||
auto limit_value = memory_limit->Accept(*eval);
|
||||
if (!limit_value.IsInt() || limit_value.ValueInt() <= 0)
|
||||
throw QueryRuntimeException("Memory limit must be a non-negative integer.");
|
||||
size_t limit = limit_value.ValueInt();
|
||||
if (std::numeric_limits<size_t>::max() / memory_scale < limit) throw QueryRuntimeException("Memory limit overflow.");
|
||||
return limit * memory_scale;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -17,26 +17,24 @@
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "expr/ast.hpp"
|
||||
#include "expr/exceptions.hpp"
|
||||
#include "expr/interpret/frame.hpp"
|
||||
#include "expr/semantic/symbol_table.hpp"
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/interpret/frame.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
struct StorageTag {};
|
||||
struct QueryEngineTag {};
|
||||
|
||||
template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId,
|
||||
typename PropertyValue, typename ConvFunctor, typename Error, typename Tag = StorageTag>
|
||||
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
public:
|
||||
ExpressionEvaluator(Frame<TypedValue> *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx,
|
||||
DbAccessor *dba, StorageView view)
|
||||
ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, DbAccessor *dba,
|
||||
storage::v3::View view)
|
||||
: frame_(frame), symbol_table_(&symbol_table), ctx_(&ctx), dba_(dba), view_(view) {}
|
||||
|
||||
using ExpressionVisitor<TypedValue>::Visit;
|
||||
@@ -54,29 +52,25 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(frame_->at(symbol_table_->at(ident)), ctx_->memory);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
TypedValue Visit(OP_NODE &op) override { \
|
||||
auto val1 = op.expression1_->Accept(*this); \
|
||||
auto val2 = op.expression2_->Accept(*this); \
|
||||
try { \
|
||||
return val1 CPP_OP val2; \
|
||||
} catch (const TypedValueException &) { \
|
||||
throw ExpressionRuntimeException("Invalid types: {} and {} for '{}'.", val1.type(), val2.type(), #CYPHER_OP); \
|
||||
} \
|
||||
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
||||
TypedValue Visit(OP_NODE &op) override { \
|
||||
auto val1 = op.expression1_->Accept(*this); \
|
||||
auto val2 = op.expression2_->Accept(*this); \
|
||||
try { \
|
||||
return val1 CPP_OP val2; \
|
||||
} catch (const TypedValueException &) { \
|
||||
throw QueryRuntimeException("Invalid types: {} and {} for '{}'.", val1.type(), val2.type(), #CYPHER_OP); \
|
||||
} \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
||||
TypedValue Visit(OP_NODE &op) override { \
|
||||
auto val = op.expression_->Accept(*this); \
|
||||
try { \
|
||||
return CPP_OP val; \
|
||||
} catch (const TypedValueException &) { \
|
||||
throw ExpressionRuntimeException("Invalid type {} for '{}'.", val.type(), #CYPHER_OP); \
|
||||
} \
|
||||
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
||||
TypedValue Visit(OP_NODE &op) override { \
|
||||
auto val = op.expression_->Accept(*this); \
|
||||
try { \
|
||||
return CPP_OP val; \
|
||||
} catch (const TypedValueException &) { \
|
||||
throw QueryRuntimeException("Invalid type {} for '{}'.", val.type(), #CYPHER_OP); \
|
||||
} \
|
||||
}
|
||||
|
||||
BINARY_OPERATOR_VISITOR(OrOperator, ||, OR);
|
||||
@@ -110,18 +104,18 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
try {
|
||||
return value1 && value2;
|
||||
} catch (const TypedValueException &) {
|
||||
throw ExpressionRuntimeException("Invalid types: {} and {} for AND.", value1.type(), value2.type());
|
||||
throw QueryRuntimeException("Invalid types: {} and {} for AND.", value1.type(), value2.type());
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(IfOperator &if_operator) override {
|
||||
auto condition = if_operator.condition_->Accept(*this);
|
||||
if (condition.IsNull()) {
|
||||
return if_operator.else_expression_->Accept(*this);
|
||||
return if_operator.then_expression_->Accept(*this);
|
||||
}
|
||||
if (condition.type() != TypedValue::Type::Bool) {
|
||||
// At the moment IfOperator is used only in CASE construct.
|
||||
throw ExpressionRuntimeException("CASE expected boolean expression, got {}.", condition.type());
|
||||
throw QueryRuntimeException("CASE expected boolean expression, got {}.", condition.type());
|
||||
}
|
||||
if (condition.ValueBool()) {
|
||||
return if_operator.then_expression_->Accept(*this);
|
||||
@@ -138,7 +132,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
// Exceptions have higher priority than returning nulls when list expression
|
||||
// is not null.
|
||||
if (_list.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("IN expected a list, got {}.", _list.type());
|
||||
throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
|
||||
}
|
||||
const auto &list = _list.ValueList();
|
||||
|
||||
@@ -168,14 +162,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
auto lhs = list_indexing.expression1_->Accept(*this);
|
||||
auto index = list_indexing.expression2_->Accept(*this);
|
||||
if (!lhs.IsList() && !lhs.IsMap() && !lhs.IsVertex() && !lhs.IsEdge() && !lhs.IsNull())
|
||||
throw ExpressionRuntimeException(
|
||||
throw QueryRuntimeException(
|
||||
"Expected a list, a map, a node or an edge to index with '[]', got "
|
||||
"{}.",
|
||||
lhs.type());
|
||||
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
||||
if (lhs.IsList()) {
|
||||
if (!index.IsInt())
|
||||
throw ExpressionRuntimeException("Expected an integer as a list index, got {}.", index.type());
|
||||
if (!index.IsInt()) throw QueryRuntimeException("Expected an integer as a list index, got {}.", index.type());
|
||||
auto index_int = index.ValueInt();
|
||||
// NOTE: Take non-const reference to list, so that we can move out the
|
||||
// indexed element as the result.
|
||||
@@ -190,8 +183,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
if (lhs.IsMap()) {
|
||||
if (!index.IsString())
|
||||
throw ExpressionRuntimeException("Expected a string as a map index, got {}.", index.type());
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a map index, got {}.", index.type());
|
||||
// NOTE: Take non-const reference to map, so that we can move out the
|
||||
// looked-up element as the result.
|
||||
auto &map = lhs.ValueMap();
|
||||
@@ -203,14 +195,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
if (lhs.IsVertex()) {
|
||||
if (!index.IsString())
|
||||
throw ExpressionRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
return TypedValue(GetProperty(lhs.ValueVertex(), index.ValueString()), ctx_->memory);
|
||||
}
|
||||
|
||||
if (lhs.IsEdge()) {
|
||||
if (!index.IsString())
|
||||
throw ExpressionRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
return TypedValue(GetProperty(lhs.ValueEdge(), index.ValueString()), ctx_->memory);
|
||||
}
|
||||
|
||||
@@ -228,7 +218,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (bound.type() == TypedValue::Type::Null) {
|
||||
is_null = true;
|
||||
} else if (bound.type() != TypedValue::Type::Int) {
|
||||
throw ExpressionRuntimeException("Expected an integer for a bound in list slicing, got {}.", bound.type());
|
||||
throw QueryRuntimeException("Expected an integer for a bound in list slicing, got {}.", bound.type());
|
||||
}
|
||||
return bound;
|
||||
}
|
||||
@@ -241,7 +231,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (_list.type() == TypedValue::Type::Null) {
|
||||
is_null = true;
|
||||
} else if (_list.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("Expected a list to slice, got {}.", _list.type());
|
||||
throw QueryRuntimeException("Expected a list to slice, got {}.", _list.type());
|
||||
}
|
||||
|
||||
if (is_null) {
|
||||
@@ -257,10 +247,9 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
auto lower_bound = normalise_bound(_lower_bound.ValueInt());
|
||||
auto upper_bound = normalise_bound(_upper_bound.ValueInt());
|
||||
if (upper_bound <= lower_bound) {
|
||||
return TypedValue(typename TypedValue::TVector(ctx_->memory), ctx_->memory);
|
||||
return TypedValue(TypedValue::TVector(ctx_->memory), ctx_->memory);
|
||||
}
|
||||
return TypedValue(
|
||||
typename TypedValue::TVector(list.begin() + lower_bound, list.begin() + upper_bound, ctx_->memory));
|
||||
return TypedValue(TypedValue::TVector(list.begin() + lower_bound, list.begin() + upper_bound, ctx_->memory));
|
||||
}
|
||||
|
||||
TypedValue Visit(IsNullOperator &is_null) override {
|
||||
@@ -328,9 +317,9 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex:
|
||||
return GetProperty(expression_result.ValueVertex(), property_lookup.property_);
|
||||
return TypedValue(GetProperty(expression_result.ValueVertex(), property_lookup.property_), ctx_->memory);
|
||||
case TypedValue::Type::Edge:
|
||||
return GetProperty(expression_result.ValueEdge(), property_lookup.property_);
|
||||
return TypedValue(GetProperty(expression_result.ValueEdge(), property_lookup.property_), ctx_->memory);
|
||||
case TypedValue::Type::Map: {
|
||||
// NOTE: Take non-const reference to map, so that we can move out the
|
||||
// looked-up element as the result.
|
||||
@@ -347,7 +336,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (auto dur_field = maybe_duration(dur, prop_name); dur_field) {
|
||||
return std::move(*dur_field);
|
||||
}
|
||||
throw ExpressionRuntimeException("Invalid property name {} for Duration", prop_name);
|
||||
throw QueryRuntimeException("Invalid property name {} for Duration", prop_name);
|
||||
}
|
||||
case TypedValue::Type::Date: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
@@ -355,7 +344,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (auto date_field = maybe_date(date, prop_name); date_field) {
|
||||
return std::move(*date_field);
|
||||
}
|
||||
throw ExpressionRuntimeException("Invalid property name {} for Date", prop_name);
|
||||
throw QueryRuntimeException("Invalid property name {} for Date", prop_name);
|
||||
}
|
||||
case TypedValue::Type::LocalTime: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
@@ -363,7 +352,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (auto lt_field = maybe_local_time(lt, prop_name); lt_field) {
|
||||
return std::move(*lt_field);
|
||||
}
|
||||
throw ExpressionRuntimeException("Invalid property name {} for LocalTime", prop_name);
|
||||
throw QueryRuntimeException("Invalid property name {} for LocalTime", prop_name);
|
||||
}
|
||||
case TypedValue::Type::LocalDateTime: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
@@ -374,50 +363,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (auto lt_field = maybe_local_time(ldt.local_time, prop_name); lt_field) {
|
||||
return std::move(*lt_field);
|
||||
}
|
||||
throw ExpressionRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
||||
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
||||
}
|
||||
default:
|
||||
throw ExpressionRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
|
||||
throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VertexAccessor, typename TTag = Tag,
|
||||
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, bool>>
|
||||
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label, StorageTag /*tag*/) {
|
||||
auto has_label = vertex.HasLabel(view_, GetLabel(label));
|
||||
if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) {
|
||||
// This is a very nasty and temporary hack in order to make MERGE
|
||||
// work. The old storage had the following logic when returning an
|
||||
// `OLD` view: `return old ? old : new`. That means that if the
|
||||
// `OLD` view didn't exist, it returned the NEW view. With this hack
|
||||
// we simulate that behavior.
|
||||
// TODO (mferencevic, teon.banek): Remove once MERGE is
|
||||
// reimplemented.
|
||||
has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label));
|
||||
}
|
||||
if (has_label.HasError()) {
|
||||
switch (has_label.GetError()) {
|
||||
case Error::DELETED_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to access labels on a deleted node.");
|
||||
case Error::NONEXISTENT_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to access labels from a node that doesn't exist.");
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
throw ExpressionRuntimeException("Unexpected error when accessing labels.");
|
||||
}
|
||||
}
|
||||
return *has_label;
|
||||
}
|
||||
|
||||
template <typename VertexAccessor, typename TTag = Tag,
|
||||
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, bool>>
|
||||
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label_ix, QueryEngineTag /*tag*/) {
|
||||
auto label = typename VertexAccessor::Label{LabelId::FromUint(label_ix.ix)};
|
||||
auto has_label = vertex.HasLabel(label);
|
||||
return !has_label;
|
||||
}
|
||||
|
||||
TypedValue Visit(LabelsTest &labels_test) override {
|
||||
auto expression_result = labels_test.expression_->Accept(*this);
|
||||
switch (expression_result.type()) {
|
||||
@@ -425,15 +377,38 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex: {
|
||||
const auto &vertex = expression_result.ValueVertex();
|
||||
if (std::ranges::all_of(labels_test.labels_, [&vertex, this](const auto label_test) {
|
||||
return this->HasLabelImpl(vertex, label_test, Tag{});
|
||||
})) {
|
||||
return TypedValue(true, ctx_->memory);
|
||||
for (const auto &label : labels_test.labels_) {
|
||||
auto has_label = vertex.HasLabel(view_, GetLabel(label));
|
||||
if (has_label.HasError() && has_label.GetError() == storage::v3::Error::NONEXISTENT_OBJECT) {
|
||||
// This is a very nasty and temporary hack in order to make MERGE
|
||||
// work. The old storage had the following logic when returning an
|
||||
// `OLD` view: `return old ? old : new`. That means that if the
|
||||
// `OLD` view didn't exist, it returned the NEW view. With this hack
|
||||
// we simulate that behavior.
|
||||
// TODO (mferencevic, teon.banek): Remove once MERGE is
|
||||
// reimplemented.
|
||||
has_label = vertex.HasLabel(storage::v3::View::NEW, GetLabel(label));
|
||||
}
|
||||
if (has_label.HasError()) {
|
||||
switch (has_label.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to access labels on a deleted node.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to access 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 QueryRuntimeException("Unexpected error when accessing labels.");
|
||||
}
|
||||
}
|
||||
if (!*has_label) {
|
||||
return TypedValue(false, ctx_->memory);
|
||||
}
|
||||
}
|
||||
return TypedValue(false, ctx_->memory);
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
default:
|
||||
throw ExpressionRuntimeException("Only nodes have labels.");
|
||||
throw QueryRuntimeException("Only nodes have labels.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,14 +419,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
TypedValue Visit(ListLiteral &literal) override {
|
||||
typename TypedValue::TVector result(ctx_->memory);
|
||||
TypedValue::TVector result(ctx_->memory);
|
||||
result.reserve(literal.elements_.size());
|
||||
for (const auto &expression : literal.elements_) result.emplace_back(expression->Accept(*this));
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(MapLiteral &literal) override {
|
||||
typename TypedValue::TMap result(ctx_->memory);
|
||||
TypedValue::TMap result(ctx_->memory);
|
||||
for (const auto &pair : literal.elements_) result.emplace(pair.first.name, pair.second->Accept(*this));
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
@@ -464,7 +439,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
auto &exprs = coalesce.expressions_;
|
||||
|
||||
if (exprs.size() == 0) {
|
||||
throw ExpressionRuntimeException("'coalesce' requires at least one argument.");
|
||||
throw QueryRuntimeException("'coalesce' requires at least one argument.");
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < exprs.size(); ++i) {
|
||||
@@ -491,7 +466,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
MG_ASSERT(res.GetMemoryResource() == ctx_->memory);
|
||||
return res;
|
||||
} else {
|
||||
typename TypedValue::TVector arguments(ctx_->memory);
|
||||
TypedValue::TVector arguments(ctx_->memory);
|
||||
arguments.reserve(function.arguments_.size());
|
||||
for (const auto &argument : function.arguments_) {
|
||||
arguments.emplace_back(argument->Accept(*this));
|
||||
@@ -508,7 +483,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("REDUCE expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("REDUCE expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &element_symbol = symbol_table_->at(*reduce.identifier_);
|
||||
@@ -528,11 +503,11 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("EXTRACT expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("EXTRACT expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &element_symbol = symbol_table_->at(*extract.identifier_);
|
||||
typename TypedValue::TVector result(ctx_->memory);
|
||||
TypedValue::TVector result(ctx_->memory);
|
||||
result.reserve(list.size());
|
||||
for (const auto &element : list) {
|
||||
if (element.IsNull()) {
|
||||
@@ -551,7 +526,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("ALL expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("ALL expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &symbol = symbol_table_->at(*all.identifier_);
|
||||
@@ -561,7 +536,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
frame_->at(symbol) = element;
|
||||
auto result = all.where_->expression_->Accept(*this);
|
||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
||||
throw ExpressionRuntimeException("Predicate of ALL must evaluate to boolean, got {}.", result.type());
|
||||
throw QueryRuntimeException("Predicate of ALL must evaluate to boolean, got {}.", result.type());
|
||||
}
|
||||
if (!result.IsNull()) {
|
||||
has_value = true;
|
||||
@@ -588,7 +563,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("SINGLE expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("SINGLE expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &symbol = symbol_table_->at(*single.identifier_);
|
||||
@@ -598,7 +573,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
frame_->at(symbol) = element;
|
||||
auto result = single.where_->expression_->Accept(*this);
|
||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
||||
throw ExpressionRuntimeException("Predicate of SINGLE must evaluate to boolean, got {}.", result.type());
|
||||
throw QueryRuntimeException("Predicate of SINGLE must evaluate to boolean, got {}.", result.type());
|
||||
}
|
||||
if (result.type() == TypedValue::Type::Bool) {
|
||||
has_value = true;
|
||||
@@ -626,7 +601,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("ANY expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("ANY expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &symbol = symbol_table_->at(*any.identifier_);
|
||||
@@ -635,7 +610,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
frame_->at(symbol) = element;
|
||||
auto result = any.where_->expression_->Accept(*this);
|
||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
||||
throw ExpressionRuntimeException("Predicate of ANY must evaluate to boolean, got {}.", result.type());
|
||||
throw QueryRuntimeException("Predicate of ANY must evaluate to boolean, got {}.", result.type());
|
||||
}
|
||||
if (!result.IsNull()) {
|
||||
has_value = true;
|
||||
@@ -658,7 +633,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw ExpressionRuntimeException("NONE expected a list, got {}.", list_value.type());
|
||||
throw QueryRuntimeException("NONE expected a list, got {}.", list_value.type());
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &symbol = symbol_table_->at(*none.identifier_);
|
||||
@@ -667,7 +642,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
frame_->at(symbol) = element;
|
||||
auto result = none.where_->expression_->Accept(*this);
|
||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
||||
throw ExpressionRuntimeException("Predicate of NONE must evaluate to boolean, got {}.", result.type());
|
||||
throw QueryRuntimeException("Predicate of NONE must evaluate to boolean, got {}.", result.type());
|
||||
}
|
||||
if (!result.IsNull()) {
|
||||
has_value = true;
|
||||
@@ -685,7 +660,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
||||
return TypedValue(conv_(ctx_->parameters.AtTokenPosition(param_lookup.token_position_)), ctx_->memory);
|
||||
return TypedValue(ctx_->parameters.AtTokenPosition(param_lookup.token_position_), ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(RegexMatch ®ex_match) override {
|
||||
@@ -695,7 +670,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (regex_value.type() != TypedValue::Type::String) {
|
||||
throw ExpressionRuntimeException("Regular expression must evaluate to a string, got {}.", regex_value.type());
|
||||
throw QueryRuntimeException("Regular expression must evaluate to a string, got {}.", regex_value.type());
|
||||
}
|
||||
if (target_string_value.type() != TypedValue::Type::String) {
|
||||
// Instead of error, we return Null which makes it compatible in case we
|
||||
@@ -708,60 +683,42 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
std::regex regex(regex_value.ValueString());
|
||||
return TypedValue(std::regex_match(target_string, regex), ctx_->memory);
|
||||
} catch (const std::regex_error &e) {
|
||||
throw ExpressionRuntimeException("Regex error in '{}': {}", regex_value.ValueString(), e.what());
|
||||
throw QueryRuntimeException("Regex error in '{}': {}", regex_value.ValueString(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, 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);
|
||||
}
|
||||
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, 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);
|
||||
}
|
||||
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
|
||||
TypedValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
||||
template <class TRecordAccessor>
|
||||
storage::v3::PropertyValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
||||
auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
|
||||
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
|
||||
if (maybe_prop.HasError() && maybe_prop.GetError() == storage::v3::Error::NONEXISTENT_OBJECT) {
|
||||
// This is a very nasty and temporary hack in order to make MERGE work.
|
||||
// The old storage had the following logic when returning an `OLD` view:
|
||||
// `return old ? old : new`. That means that if the `OLD` view didn't
|
||||
// exist, it returned the NEW view. With this hack we simulate that
|
||||
// behavior.
|
||||
// TODO (mferencevic, teon.banek): Remove once MERGE is reimplemented.
|
||||
maybe_prop = record_accessor.GetProperty(StorageView::NEW, ctx_->properties[prop.ix]);
|
||||
maybe_prop = record_accessor.GetProperty(storage::v3::View::NEW, ctx_->properties[prop.ix]);
|
||||
}
|
||||
if (maybe_prop.HasError()) {
|
||||
switch (maybe_prop.GetError()) {
|
||||
case Error::DELETED_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to get a property from a deleted object.");
|
||||
case Error::NONEXISTENT_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to get a property from an object that doesn't exist.");
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to get a property from a deleted object.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get a property from an object 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 a property.");
|
||||
}
|
||||
}
|
||||
return conv_(*maybe_prop, ctx_->memory);
|
||||
return *maybe_prop;
|
||||
}
|
||||
|
||||
template <class TRecordAccessor, class TTag = Tag,
|
||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
|
||||
TypedValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
|
||||
template <class TRecordAccessor>
|
||||
storage::v3::PropertyValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
|
||||
auto maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
|
||||
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
|
||||
if (maybe_prop.HasError() && maybe_prop.GetError() == storage::v3::Error::NONEXISTENT_OBJECT) {
|
||||
// This is a very nasty and temporary hack in order to make MERGE work.
|
||||
// The old storage had the following logic when returning an `OLD` view:
|
||||
// `return old ? old : new`. That means that if the `OLD` view didn't
|
||||
@@ -772,55 +729,36 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
if (maybe_prop.HasError()) {
|
||||
switch (maybe_prop.GetError()) {
|
||||
case Error::DELETED_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to get a property from a deleted object.");
|
||||
case Error::NONEXISTENT_OBJECT:
|
||||
throw ExpressionRuntimeException("Trying to get a property from an object that doesn't exist.");
|
||||
case Error::SERIALIZATION_ERROR:
|
||||
case Error::VERTEX_HAS_EDGES:
|
||||
case Error::PROPERTIES_DISABLED:
|
||||
throw ExpressionRuntimeException("Unexpected error when getting a property.");
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to get a property from a deleted object.");
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw query::v2::QueryRuntimeException("Trying to get a property from an object 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 a property.");
|
||||
}
|
||||
}
|
||||
return conv_(*maybe_prop, ctx_->memory);
|
||||
return *maybe_prop;
|
||||
}
|
||||
|
||||
LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
|
||||
storage::v3::LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
|
||||
|
||||
Frame<TypedValue> *frame_;
|
||||
Frame *frame_;
|
||||
const SymbolTable *symbol_table_;
|
||||
const EvaluationContext *ctx_;
|
||||
DbAccessor *dba_;
|
||||
// which switching approach should be used when evaluating
|
||||
StorageView view_;
|
||||
ConvFunctor conv_;
|
||||
storage::v3::View view_;
|
||||
};
|
||||
|
||||
/// A helper function for evaluating an expression that's an int.
|
||||
///
|
||||
/// @param what - Name of what's getting evaluated. Used for user feedback (via
|
||||
/// exception) when the evaluated value is not an int.
|
||||
/// @throw ExpressionRuntimeException if expression doesn't evaluate to an int.
|
||||
template <typename ExpressionEvaluator>
|
||||
int64_t EvaluateInt(ExpressionEvaluator *evaluator, Expression *expr, const std::string &what) {
|
||||
TypedValue value = expr->Accept(*evaluator);
|
||||
try {
|
||||
return value.ValueInt();
|
||||
} catch (TypedValueException &e) {
|
||||
throw ExpressionRuntimeException(what + " must be an int");
|
||||
}
|
||||
}
|
||||
/// @throw QueryRuntimeException if expression doesn't evaluate to an int.
|
||||
int64_t EvaluateInt(ExpressionEvaluator *evaluator, Expression *expr, const std::string &what);
|
||||
|
||||
template <typename ExpressionEvaluator>
|
||||
std::optional<size_t> EvaluateMemoryLimit(ExpressionEvaluator *eval, Expression *memory_limit, size_t memory_scale) {
|
||||
if (!memory_limit) return std::nullopt;
|
||||
auto limit_value = memory_limit->Accept(*eval);
|
||||
if (!limit_value.IsInt() || limit_value.ValueInt() <= 0)
|
||||
throw ExpressionRuntimeException("Memory limit must be a non-negative integer.");
|
||||
size_t limit = limit_value.ValueInt();
|
||||
if (std::numeric_limits<size_t>::max() / memory_scale < limit)
|
||||
throw ExpressionRuntimeException("Memory limit overflow.");
|
||||
return limit * memory_scale;
|
||||
}
|
||||
std::optional<size_t> EvaluateMemoryLimit(ExpressionEvaluator *eval, Expression *memory_limit, size_t memory_scale);
|
||||
|
||||
} // namespace memgraph::expr
|
||||
} // namespace memgraph::query::v2
|
||||
@@ -13,14 +13,14 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "expr/semantic/symbol_table.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
|
||||
namespace memgraph::expr {
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
template <typename TypedValue>
|
||||
class Frame {
|
||||
public:
|
||||
/// Create a Frame of given size backed by a utils::NewDeleteResource()
|
||||
@@ -42,4 +42,4 @@ class Frame {
|
||||
utils::pmr::vector<TypedValue> elems_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::expr
|
||||
} // namespace memgraph::query::v2
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,21 +14,23 @@
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "query/v2/auth_checker.hpp"
|
||||
#include "query/v2/bindings/cypher_main_visitor.hpp"
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/config.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/cypher_query_interpreter.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/v2/frontend/stripped.hpp"
|
||||
#include "query/v2/interpret/frame.hpp"
|
||||
#include "query/v2/metadata.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/read_write_type_checker.hpp"
|
||||
#include "query/v2/stream.hpp"
|
||||
#include "query/v2/stream/streams.hpp"
|
||||
#include "query/v2/trigger.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
@@ -164,10 +166,10 @@ struct PreparedQuery {
|
||||
* been passed to an `Interpreter` instance.
|
||||
*/
|
||||
struct InterpreterContext {
|
||||
explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config,
|
||||
explicit InterpreterContext(storage::v3::Storage *db, InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory);
|
||||
|
||||
storage::v3::Shard *db;
|
||||
storage::v3::Storage *db;
|
||||
|
||||
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
|
||||
std::atomic<bool> is_shutting_down{false};
|
||||
@@ -178,23 +180,12 @@ struct InterpreterContext {
|
||||
utils::SkipList<QueryCacheEntry> ast_cache;
|
||||
utils::SkipList<PlanCacheEntry> plan_cache;
|
||||
|
||||
TriggerStore trigger_store;
|
||||
utils::ThreadPool after_commit_trigger_pool{1};
|
||||
|
||||
const InterpreterConfig config;
|
||||
|
||||
storage::v3::LabelId NameToLabelId(std::string_view label_name) {
|
||||
return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name));
|
||||
}
|
||||
|
||||
storage::v3::PropertyId NameToPropertyId(std::string_view property_name) {
|
||||
return storage::v3::PropertyId::FromUint(query_id_mapper.NameToId(property_name));
|
||||
}
|
||||
|
||||
storage::v3::EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) {
|
||||
return storage::v3::EdgeTypeId::FromUint(query_id_mapper.NameToId(edge_type_name));
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO Replace with local map of labels, properties and edge type ids
|
||||
storage::v3::NameIdMapper query_id_mapper;
|
||||
query::v2::stream::Streams streams;
|
||||
};
|
||||
|
||||
/// Function that is used to tell all active interpreters that they should stop
|
||||
@@ -325,8 +316,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 shrared_ptr.
|
||||
std::unique_ptr<storage::v3::Shard::Accessor> db_accessor_;
|
||||
std::unique_ptr<storage::v3::Storage::Accessor> db_accessor_;
|
||||
std::optional<DbAccessor> execution_db_accessor_;
|
||||
std::optional<TriggerContextCollector> trigger_context_collector_;
|
||||
bool in_explicit_transaction_{false};
|
||||
bool expect_rollback_{false};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "query/v2/bindings/typed_value.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/parameters.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "storage/v3/conversions.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
|
||||
namespace memgraph::query::v2::plan {
|
||||
|
||||
@@ -249,7 +248,7 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
// return nullopt.
|
||||
std::optional<storage::v3::PropertyValue> ConstPropertyValue(const Expression *expression) {
|
||||
if (auto *literal = utils::Downcast<const PrimitiveLiteral>(expression)) {
|
||||
return storage::v3::TypedToPropertyValue(literal->value_);
|
||||
return literal->value_;
|
||||
} else if (auto *param_lookup = utils::Downcast<const ParameterLookup>(expression)) {
|
||||
return parameters.AtTokenPosition(param_lookup->token_position_);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,21 +24,14 @@
|
||||
|
||||
#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"
|
||||
#include "query/v2/frontend/semantic/symbol.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/fnv.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/visitor.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "query/v2/accessors.hpp"
|
||||
|
||||
using VertexAccessor = memgraph::query::v2::accessors::VertexAccessor;
|
||||
using EdgeAccessor = memgraph::query::v2::accessors::EdgeAccessor;
|
||||
using Path = memgraph::query::v2::accessors::Path;
|
||||
cpp<#
|
||||
|
||||
(lcp:namespace memgraph)
|
||||
@@ -47,6 +40,9 @@ cpp<#
|
||||
|
||||
#>cpp
|
||||
struct ExecutionContext;
|
||||
class ExpressionEvaluator;
|
||||
class Frame;
|
||||
class SymbolTable;
|
||||
cpp<#
|
||||
|
||||
(lcp:namespace plan)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "query/v2/bindings/symbol_table.hpp"
|
||||
#include "query/v2/plan/cost_estimator.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query/v2/plan/preprocess.hpp"
|
||||
@@ -30,6 +29,7 @@
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
class AstStorage;
|
||||
class SymbolTable;
|
||||
|
||||
namespace plan {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user