Compare commits
24 Commits
memory-com
...
add-transf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e71b9a99ae | ||
|
|
2aa6b9ba8f | ||
|
|
6c9cdef944 | ||
|
|
5c10fb1d51 | ||
|
|
e73b3feea3 | ||
|
|
2ed40e9f16 | ||
|
|
ced34bc79c | ||
|
|
2b3540119f | ||
|
|
3936668e13 | ||
|
|
476080bace | ||
|
|
8528822af1 | ||
|
|
7ecf970dcd | ||
|
|
6ad5463d72 | ||
|
|
b1d560737e | ||
|
|
b719f0744f | ||
|
|
440838c0e9 | ||
|
|
79a3c5af8e | ||
|
|
bf03b38e39 | ||
|
|
fd63944493 | ||
|
|
6694de2dfa | ||
|
|
5e5f4ffc5d | ||
|
|
060b9d1c16 | ||
|
|
29a505cb38 | ||
|
|
d4fcd745d2 |
5
.github/workflows/diff.yaml
vendored
5
.github/workflows/diff.yaml
vendored
@@ -229,6 +229,11 @@ jobs:
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check e2e service dependencies
|
||||
run: |
|
||||
cd tests/e2e
|
||||
./dependency_check.sh
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
|
||||
77
.github/workflows/performance_benchmarks.yaml
vendored
Normal file
77
.github/workflows/performance_benchmarks.yaml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Run performance benchmarks manually
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
performance_benchmarks:
|
||||
name: "Performance benchmarks"
|
||||
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build only memgraph release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Get branch name (merge)
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get branch name (pull request)
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Run benchmarks
|
||||
run: |
|
||||
cd tests/mgbench
|
||||
./benchmark.py vendor-native --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
|
||||
|
||||
./benchmark.py vendor-native --num-workers-for-benchmark 1 --export-results benchmark_supernode.json supernode
|
||||
|
||||
./benchmark.py vendor-native --num-workers-for-benchmark 1 --export-results benchmark_high_write_set_property.json high_write_set_property
|
||||
|
||||
- name: Upload benchmark results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
virtualenv -p python3 ve3
|
||||
source ve3/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./main.py --benchmark-name "mgbench" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_result.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
./main.py --benchmark-name "supernode" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_supernode.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
./main.py --benchmark-name "high_write_set_property" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_high_write_set_property.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
@@ -107,6 +107,10 @@ modifications:
|
||||
value: "false"
|
||||
override: true
|
||||
|
||||
- name: "query_callable_mappings_path"
|
||||
value: "/etc/memgraph/apoc_compatibility_mappings.json"
|
||||
override: true
|
||||
|
||||
undocumented:
|
||||
- "flag_file"
|
||||
- "also_log_to_stderr"
|
||||
|
||||
27
config/mappings.json
Normal file
27
config/mappings.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"dbms.components": "mgps.components",
|
||||
"apoc.util.validate": "mgps.validate",
|
||||
"db.schema.nodeTypeProperties":"schema.node_type_properties",
|
||||
"db.schema.relTypeProperties":"schema.rel_type_properties",
|
||||
"apoc.coll.contains": "collections.contains",
|
||||
"apoc.coll.partition": "collections.partition",
|
||||
"apoc.coll.toSet": "collections.to_set",
|
||||
"apoc.coll.unionAll": "collections.unionAll",
|
||||
"apoc.coll.removeAll": "collections.remove_all",
|
||||
"apoc.coll.union": "collections.union",
|
||||
"apoc.coll.sum": "collections.sum",
|
||||
"apoc.coll.pairs": "collections.pairs",
|
||||
"apoc.map.fromLists": "map.from_lists",
|
||||
"apoc.map.removeKeys": "map.remove_keys",
|
||||
"apoc.map.merge": "map.merge",
|
||||
"apoc.create.nodes": "create.nodes",
|
||||
"apoc.create.removeProperties": "create.remove_properties",
|
||||
"apoc.create.node": "create.node",
|
||||
"apoc.create.removeLabel": "create.remove_label",
|
||||
"apoc.refactor.invert": "refactor.invert",
|
||||
"apoc.refactor.cloneNode": "refactor.clone_node",
|
||||
"apoc.refactor.cloneSubgraph": "refactor.clone_subgraph",
|
||||
"apoc.refactor.cloneSubgraphFromPath": "refactor.clone_subgraph_from_path",
|
||||
"apoc.label.exists": "label.exists"
|
||||
|
||||
}
|
||||
@@ -782,4 +782,44 @@ inline void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_me
|
||||
MgInvokeVoid(mgp_func_result_set_value, res, value, memory);
|
||||
}
|
||||
|
||||
// Messages
|
||||
|
||||
inline mgp_source_type message_source_type(struct mgp_message *message) {
|
||||
return MgInvoke<mgp_source_type>(mgp_message_source_type, message);
|
||||
}
|
||||
|
||||
inline const char *message_payload(struct mgp_message *message) {
|
||||
return MgInvoke<const char *>(mgp_message_payload, message);
|
||||
}
|
||||
|
||||
inline size_t message_payload_size(struct mgp_message *message) {
|
||||
return MgInvoke<size_t>(mgp_message_payload_size, message);
|
||||
}
|
||||
|
||||
inline const char *message_topic_name(struct mgp_message *message) {
|
||||
return MgInvoke<const char *>(mgp_message_topic_name, message);
|
||||
}
|
||||
|
||||
inline const char *message_key(struct mgp_message *message) { return MgInvoke<const char *>(mgp_message_key, message); }
|
||||
|
||||
inline size_t message_key_size(struct mgp_message *message) { return MgInvoke<size_t>(mgp_message_key_size, message); }
|
||||
|
||||
inline int64_t message_timestamp(struct mgp_message *message) {
|
||||
return MgInvoke<int64_t>(mgp_message_timestamp, message);
|
||||
}
|
||||
|
||||
inline int64_t message_offset(struct mgp_message *message) { return MgInvoke<int64_t>(mgp_message_offset, message); }
|
||||
|
||||
inline size_t messages_size(struct mgp_messages *message) { return MgInvoke<size_t>(mgp_messages_size, message); }
|
||||
|
||||
inline mgp_message *messages_at(struct mgp_messages *message, size_t index) {
|
||||
return MgInvoke<mgp_message *>(mgp_messages_at, message, index);
|
||||
}
|
||||
|
||||
// Transformation
|
||||
|
||||
inline void module_add_transformation(struct mgp_module *module, const char *name, mgp_trans_cb cb) {
|
||||
return MgInvokeVoid(mgp_module_add_transformation, module, name, cb);
|
||||
}
|
||||
|
||||
} // namespace mgp
|
||||
|
||||
209
include/mgp.hpp
209
include/mgp.hpp
@@ -1406,6 +1406,8 @@ class Record {
|
||||
void Insert(const char *field_name, const Duration &duration);
|
||||
/// @brief Inserts a @ref Value value under field `field_name`, and then call appropriate insert.
|
||||
void Insert(const char *field_name, const Value &value);
|
||||
/// @brief Inserts a @ref null value under field `field_name`.
|
||||
void Insert(const char *field_name);
|
||||
|
||||
private:
|
||||
mgp_result_record *record_;
|
||||
@@ -1533,6 +1535,99 @@ enum class ProcedureType : uint8_t {
|
||||
Write,
|
||||
};
|
||||
|
||||
enum class StreamSourceType : uint8_t { Kafka, Pulsar };
|
||||
|
||||
class Message {
|
||||
public:
|
||||
explicit Message(mgp_message *ptr);
|
||||
explicit Message(const mgp_message *const_ptr);
|
||||
|
||||
Message(const Message &other) noexcept;
|
||||
Message(Message &&other) noexcept;
|
||||
|
||||
Message &operator=(const Message &other) noexcept;
|
||||
Message &operator=(Message &&other) noexcept;
|
||||
|
||||
~Message();
|
||||
|
||||
StreamSourceType SourceType() const;
|
||||
std::string Payload() const;
|
||||
size_t PayloadSize() const;
|
||||
std::string TopicName() const;
|
||||
std::string Key() const;
|
||||
size_t KeySize() const;
|
||||
int64_t Timestamp() const;
|
||||
int64_t Offset() const;
|
||||
|
||||
private:
|
||||
mgp_message *ptr_;
|
||||
};
|
||||
|
||||
class Messages {
|
||||
public:
|
||||
explicit Messages(mgp_messages *ptr);
|
||||
explicit Messages(const mgp_messages *const_ptr);
|
||||
|
||||
Messages(const Messages &other) noexcept;
|
||||
Messages(Messages &&other) noexcept;
|
||||
|
||||
Messages &operator=(const Messages &other) noexcept;
|
||||
Messages &operator=(Messages &&other) noexcept;
|
||||
|
||||
~Messages();
|
||||
|
||||
/// @brief Returns the size of the list.
|
||||
size_t Size() const;
|
||||
/// @brief Returns whether the list is empty.
|
||||
bool Empty() const;
|
||||
|
||||
/// @brief Returns the value at the given `index`.
|
||||
const Message operator[](size_t index) const;
|
||||
|
||||
///@brief Same as above, but non const value
|
||||
Message operator[](size_t index);
|
||||
|
||||
class Iterator {
|
||||
private:
|
||||
friend class Messages;
|
||||
|
||||
public:
|
||||
using value_type = Messages;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const Messages *;
|
||||
using reference = const Messages &;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
bool operator==(const Iterator &other) const;
|
||||
|
||||
bool operator!=(const Iterator &other) const;
|
||||
|
||||
Iterator &operator++();
|
||||
|
||||
const Message operator*() const;
|
||||
|
||||
private:
|
||||
Iterator(const Messages *iterable, size_t index);
|
||||
|
||||
const Messages *iterable_;
|
||||
size_t index_;
|
||||
};
|
||||
|
||||
Iterator begin() const;
|
||||
Iterator end() const;
|
||||
|
||||
Iterator cbegin() const;
|
||||
Iterator cend() const;
|
||||
|
||||
/// @exception std::runtime_error List contains value of unknown type.
|
||||
bool operator==(const Messages &other) const;
|
||||
/// @exception std::runtime_error List contains value of unknown type.
|
||||
bool operator!=(const Messages &other) const;
|
||||
|
||||
private:
|
||||
mgp_messages *ptr_;
|
||||
};
|
||||
|
||||
/// @brief Adds a procedure to the query module.
|
||||
/// @param callback - procedure callback
|
||||
/// @param name - procedure name
|
||||
@@ -1568,6 +1663,8 @@ inline void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initial
|
||||
inline void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<Parameter> parameters,
|
||||
mgp_module *module, mgp_memory *memory);
|
||||
|
||||
inline void AddTransformation(mgp_trans_cb callback, std::string_view name, mgp_module *module);
|
||||
|
||||
/* #endregion */
|
||||
|
||||
namespace util {
|
||||
@@ -1794,6 +1891,34 @@ inline bool ValuesEqual(mgp_value *value1, mgp_value *value2) {
|
||||
throw ValueException("Invalid value; does not match any Memgraph type.");
|
||||
}
|
||||
|
||||
inline bool MessageEqual(mgp_message *message1, mgp_message *message2) {
|
||||
if (mgp::message_source_type(message1) != mgp::message_source_type(message2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mgp::message_payload_size(message1) != mgp::message_payload_size(message2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mgp::message_payload(message1) == mgp::message_payload(message2);
|
||||
}
|
||||
|
||||
inline bool MessagesEqual(mgp_messages *messages1, mgp_messages *messages2) {
|
||||
if (messages1 == messages2) {
|
||||
return true;
|
||||
}
|
||||
if (mgp::messages_size(messages1) != mgp::messages_size(messages2)) {
|
||||
return false;
|
||||
}
|
||||
const size_t len = mgp::messages_size(messages1);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (!util::MessageEqual(mgp::messages_at(messages1, i), mgp::messages_at(messages2, i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Converts C++ API types to their MGP API equivalents.
|
||||
inline mgp_type *ToMGPType(Type type) {
|
||||
switch (type) {
|
||||
@@ -3948,6 +4073,12 @@ inline const std::string Value::ToString() const {
|
||||
|
||||
inline Record::Record(mgp_result_record *record) : record_(record) {}
|
||||
|
||||
inline void Record::Insert(const char *field_name) {
|
||||
auto null_value = mgp::MemHandlerCallback(value_make_null);
|
||||
{ mgp::result_record_insert(record_, field_name, null_value); }
|
||||
mgp::value_destroy(null_value);
|
||||
}
|
||||
|
||||
inline void Record::Insert(const char *field_name, bool value) {
|
||||
auto mgp_val = mgp::MemHandlerCallback(value_make_bool, value);
|
||||
{ mgp::result_record_insert(record_, field_name, mgp_val); }
|
||||
@@ -4034,6 +4165,8 @@ inline void Record::Insert(const char *field_name, const Duration &duration) {
|
||||
|
||||
inline void Record::Insert(const char *field_name, const Value &value) {
|
||||
switch (value.Type()) {
|
||||
case Type::Null:
|
||||
return Insert(field_name);
|
||||
case Type::Bool:
|
||||
return Insert(field_name, value.ValueBool());
|
||||
case Type::Int:
|
||||
@@ -4241,6 +4374,78 @@ inline mgp_type *Return::GetMGPType() const {
|
||||
return util::ToMGPType(type_);
|
||||
}
|
||||
|
||||
// Message
|
||||
|
||||
inline Message::Message(mgp_message *ptr) : ptr_(ptr) {}
|
||||
inline Message::Message(const mgp_message *const_ptr) : ptr_(const_cast<mgp_message *>(const_ptr)) {}
|
||||
inline Message::Message(const Message &other) noexcept : Message(other.ptr_) {}
|
||||
inline Message::Message(Message &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
inline Message &Message::operator=(Message &&other) noexcept {
|
||||
if (this != &other) {
|
||||
ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline Message &Message::operator=(const Message &other) noexcept { return *this; }
|
||||
inline Message::~Message() { ptr_ = nullptr; }
|
||||
inline StreamSourceType Message::SourceType() const {
|
||||
auto result = mgp::message_source_type(ptr_);
|
||||
switch (result) {
|
||||
case mgp_source_type::KAFKA:
|
||||
return StreamSourceType::Kafka;
|
||||
case mgp_source_type::PULSAR:
|
||||
return StreamSourceType::Pulsar;
|
||||
}
|
||||
}
|
||||
inline std::string Message::Payload() const { return std::string{mgp::message_payload(ptr_)}; }
|
||||
inline size_t Message::PayloadSize() const { return Payload().size(); }
|
||||
inline std::string Message::TopicName() const { return std::string{mgp::message_topic_name(ptr_)}; }
|
||||
inline std::string Message::Key() const { return std::string{mgp::message_key(ptr_)}; }
|
||||
inline size_t Message::KeySize() const { return Key().size(); }
|
||||
inline int64_t Message::Timestamp() const { return mgp::message_timestamp(ptr_); }
|
||||
inline int64_t Message::Offset() const { return mgp::message_offset(ptr_); }
|
||||
|
||||
// Messages
|
||||
|
||||
inline Messages::Messages(mgp_messages *ptr) : ptr_(ptr) {}
|
||||
inline Messages::Messages(const mgp_messages *const_ptr) : ptr_(const_cast<mgp_messages *>(const_ptr)) {}
|
||||
inline Messages::Messages(const Messages &other) noexcept : Messages(other.ptr_) {}
|
||||
inline Messages::Messages(Messages &&other) noexcept : Messages(other.ptr_) { other.ptr_ = nullptr; }
|
||||
inline Messages &Messages::operator=(const Messages &other) noexcept { return *this; }
|
||||
inline Messages &Messages::operator=(Messages &&other) noexcept {
|
||||
if (this != &other) {
|
||||
ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline Messages::~Messages() { ptr_ = nullptr; }
|
||||
inline size_t Messages::Size() const { return mgp::messages_size(ptr_); }
|
||||
inline bool Messages::Empty() const { return mgp::messages_size(ptr_) == 0; }
|
||||
inline const Message Messages::operator[](size_t index) const { return Message(mgp::messages_at(ptr_, index)); }
|
||||
inline Message Messages::operator[](size_t index) { return Message(mgp::messages_at(ptr_, index)); }
|
||||
|
||||
inline Messages::Iterator Messages::begin() const { return Messages::Iterator(this, 0); }
|
||||
inline Messages::Iterator Messages::end() const { return Messages::Iterator(this, Size()); }
|
||||
inline Messages::Iterator Messages::cbegin() const { return Messages::Iterator(this, 0); }
|
||||
inline Messages::Iterator Messages::cend() const { return Messages::Iterator(this, Size()); }
|
||||
|
||||
inline bool Messages::operator==(const Messages &other) const { return util::MessagesEqual(ptr_, other.ptr_); }
|
||||
|
||||
inline bool Messages::operator!=(const Messages &other) const { return !(*this == other); }
|
||||
|
||||
inline bool Messages::Iterator::operator==(const Messages::Iterator &other) const {
|
||||
return this->iterable_ == other.iterable_ && this->index_ == other.index_;
|
||||
}
|
||||
inline bool Messages::Iterator::operator!=(const Messages::Iterator &other) const { return !(*this == other); }
|
||||
inline Messages::Iterator &Messages::Iterator::operator++() {
|
||||
index_++;
|
||||
return *this;
|
||||
}
|
||||
inline const Message Messages::Iterator::operator*() const { return (*iterable_)[index_]; }
|
||||
inline Messages::Iterator::Iterator(const Messages *iterable, size_t index) : iterable_(iterable), index_(index) {}
|
||||
|
||||
// do not enter
|
||||
namespace detail {
|
||||
inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> ¶meters,
|
||||
@@ -4293,6 +4498,10 @@ void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<Parame
|
||||
}
|
||||
}
|
||||
|
||||
void AddTransformation(mgp_trans_cb callback, std::string_view name, mgp_module *module) {
|
||||
mgp::module_add_transformation(module, name.data(), callback);
|
||||
}
|
||||
|
||||
/* #endregion */
|
||||
|
||||
} // namespace mgp
|
||||
|
||||
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
|
||||
3. using the Licensed Work to create a work or solution
|
||||
which competes (or might reasonably be expected to
|
||||
compete) with the Licensed Work.
|
||||
CHANGE DATE: 2027-02-08
|
||||
CHANGE DATE: 2027-13-09
|
||||
CHANGE LICENSE: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||
|
||||
@@ -6,6 +6,8 @@ project(memgraph_query_modules)
|
||||
|
||||
disallow_in_source_build()
|
||||
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||
|
||||
# Everything that is installed here, should be under the "query_modules" component.
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "query_modules")
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
@@ -37,9 +39,25 @@ endif()
|
||||
install(PROGRAMS $<TARGET_FILE:example_cpp>
|
||||
DESTINATION lib/memgraph/query_modules
|
||||
RENAME example_cpp.so)
|
||||
|
||||
# Also install the source of the example, so user can read it.
|
||||
install(FILES example.cpp DESTINATION lib/memgraph/query_modules/src)
|
||||
|
||||
add_library(example_cpp_transformation SHARED example_transformation.cpp)
|
||||
target_include_directories(example_cpp_transformation PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_options(example_cpp_transformation PRIVATE -Wall)
|
||||
# Strip C++ transformation example in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET example_cpp_transformation POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:example_cpp_transformation>
|
||||
COMMENT "Stripping symbols and sections from the C++ transformation example module")
|
||||
endif()
|
||||
install(PROGRAMS $<TARGET_FILE:example_cpp_transformation>
|
||||
DESTINATION lib/memgraph/query_modules)
|
||||
|
||||
# Also install the source of the example, so user can read it.
|
||||
install(FILES example_cpp_transformation.cpp DESTINATION lib/memgraph/query_modules/src)
|
||||
|
||||
# Install the Python example and modules
|
||||
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
|
||||
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)
|
||||
|
||||
64
query_modules/example_transformation.cpp
Normal file
64
query_modules/example_transformation.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <exception>
|
||||
#include <mgp.hpp>
|
||||
|
||||
static constexpr std::string_view kQuery = "query";
|
||||
static constexpr std::string_view kParameters = "parameters";
|
||||
|
||||
std::string EscapeString(std::string s) {
|
||||
std::string sign = "'";
|
||||
std::string replace_sign;
|
||||
|
||||
size_t pos;
|
||||
while ((pos = s.find(sign)) != std::string::npos) {
|
||||
s.replace(pos, 1, replace_sign);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void Transformation(struct mgp_messages *messages, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
|
||||
mgp::MemoryDispatcherGuard guard(memory);
|
||||
auto record_factory = mgp::RecordFactory(result);
|
||||
try {
|
||||
auto stream_messages = mgp::Messages(messages);
|
||||
|
||||
for (const mgp::Message &message : stream_messages) {
|
||||
auto record = record_factory.NewRecord();
|
||||
|
||||
auto payload = EscapeString(message.Payload());
|
||||
|
||||
auto query = "CREATE (:Data {payload: '" + payload + "'});";
|
||||
auto query_value = mgp::Value(query.data());
|
||||
|
||||
record.Insert(kQuery.data(), query_value);
|
||||
record.Insert(kParameters.data(), mgp::Value());
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
record_factory.SetErrorMessage(ex.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int mgp_init_module(mgp_module *module, mgp_memory *memory) {
|
||||
try {
|
||||
mgp::MemoryDispatcherGuard guard(memory);
|
||||
mgp::AddTransformation(Transformation, "transform", module);
|
||||
} catch (const std::exception &e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int mgp_shutdown_module() { return 0; }
|
||||
@@ -1,3 +1,4 @@
|
||||
/etc/memgraph/memgraph.conf
|
||||
/etc/memgraph/apoc_compatibility_mappings.json
|
||||
/etc/memgraph/auth_module/ldap.example.yaml
|
||||
/etc/logrotate.d/memgraph
|
||||
|
||||
@@ -132,6 +132,7 @@ echo "Don't forget to switch to the 'memgraph' user to use Memgraph" || exit 1
|
||||
# Override CPACK_RPM_ABSOLUTE_INSTALL_FILES with our %config(noreplace), cpack
|
||||
# uses plain %config.
|
||||
%config(noreplace) "/etc/memgraph/memgraph.conf"
|
||||
%config(noreplace) "/etc/memgraph/apoc_compatibility_mappings.json"
|
||||
%config(noreplace) "/etc/memgraph/auth_module/ldap.example.yaml"
|
||||
%config(noreplace) "/etc/logrotate.d/memgraph"
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ add_subdirectory(rpc)
|
||||
add_subdirectory(license)
|
||||
add_subdirectory(auth)
|
||||
add_subdirectory(audit)
|
||||
add_subdirectory(flags)
|
||||
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
|
||||
@@ -31,19 +32,13 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# ----------------------------------------------------------------------------
|
||||
set(mg_single_node_v2_sources
|
||||
memgraph.cpp
|
||||
flags/isolation_level.cpp
|
||||
flags/memory_limit.cpp
|
||||
flags/log_level.cpp
|
||||
flags/general.cpp
|
||||
flags/audit.cpp
|
||||
flags/bolt.cpp
|
||||
)
|
||||
|
||||
# memgraph main executable
|
||||
add_executable(memgraph ${mg_single_node_v2_sources})
|
||||
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(memgraph stdc++fs Threads::Threads
|
||||
mg-telemetry mg-communication mg-memory mg-utils mg-license mg-settings mg-glue)
|
||||
mg-telemetry mg-communication mg-memory mg-utils mg-license mg-settings mg-glue mg-flags)
|
||||
|
||||
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
|
||||
# should be dynamically exported, so that `dlopen` can correctly link the
|
||||
@@ -76,7 +71,7 @@ if(lower_build_type STREQUAL "release")
|
||||
COMMENT "Stripping symbols and sections from memgraph")
|
||||
endif()
|
||||
|
||||
# Generate the configuration file.
|
||||
# Generate the configuration file under the build directory.
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||
${CMAKE_BINARY_DIR}/memgraph
|
||||
@@ -85,6 +80,11 @@ add_custom_command(TARGET memgraph POST_BUILD
|
||||
${CMAKE_SOURCE_DIR}/config/flags.yaml
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
COMMENT "Generating memgraph configuration file")
|
||||
# Copy the mappings file to the build directory.
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/config/mappings.json
|
||||
${CMAKE_BINARY_DIR}/config/apoc_compatibility_mappings.json)
|
||||
|
||||
# Everything here is under "memgraph" install component.
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "memgraph")
|
||||
@@ -120,6 +120,9 @@ install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.hpp
|
||||
# Install the config file (must use absolute path).
|
||||
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
DESTINATION /etc/memgraph RENAME memgraph.conf)
|
||||
# Install the mappings file (must use absolute path).
|
||||
install(FILES ${CMAKE_BINARY_DIR}/config/apoc_compatibility_mappings.json
|
||||
DESTINATION /etc/memgraph RENAME apoc_compatibility_mappings.json)
|
||||
|
||||
# Install logrotate configuration (must use absolute path).
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate.conf
|
||||
|
||||
@@ -159,6 +159,7 @@ class Session : public dbms::SessionInterface {
|
||||
break;
|
||||
case State::Idle:
|
||||
case State::Result:
|
||||
at_least_one_run_ = true;
|
||||
state_ = StateExecutingRun(*this, state_);
|
||||
break;
|
||||
case State::Error:
|
||||
@@ -180,6 +181,12 @@ class Session : public dbms::SessionInterface {
|
||||
}
|
||||
}
|
||||
|
||||
void HandleError() {
|
||||
if (!at_least_one_run_) {
|
||||
spdlog::info("Sudden connection loss. Make sure the client supports Memgraph.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rethink if there is a way to hide some members. At the momement all of them are public.
|
||||
TInputStream &input_stream_;
|
||||
TOutputStream &output_stream_;
|
||||
@@ -192,6 +199,7 @@ class Session : public dbms::SessionInterface {
|
||||
|
||||
bool handshake_done_{false};
|
||||
State state_{State::Handshake};
|
||||
bool at_least_one_run_{false};
|
||||
|
||||
struct Version {
|
||||
uint8_t major;
|
||||
|
||||
@@ -174,7 +174,7 @@ State SendSuccessMessage(TSession &session) {
|
||||
// we send a hardcoded value for now.
|
||||
std::map<std::string, Value> metadata{{"connection_id", "bolt-1"}};
|
||||
if (auto server_name = session.GetServerNameForInit(); server_name) {
|
||||
metadata.insert({"server", *server_name});
|
||||
metadata.insert({"server", std::move(*server_name)});
|
||||
}
|
||||
bool success_sent = session.encoder_.MessageSuccess(metadata);
|
||||
if (!success_sent) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -15,6 +15,10 @@
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
// Centos 7 OpenSSL includes libkrb5 which has brings in macros TRUE and FALSE. undef to prevent issues.
|
||||
#undef TRUE
|
||||
#undef FALSE
|
||||
|
||||
#include "communication/buffer.hpp"
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/init.hpp"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -17,6 +17,10 @@
|
||||
#include <openssl/ssl.h>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
// Centos 7 OpenSSL includes libkrb5 which has brings in macros TRUE and FALSE. undef to prevent issues.
|
||||
#undef TRUE
|
||||
#undef FALSE
|
||||
|
||||
namespace memgraph::communication {
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
// Centos 7 OpenSSL includes libkrb5 which has brings in macros TRUE and FALSE. undef to prevent issues.
|
||||
#undef TRUE
|
||||
#undef FALSE
|
||||
|
||||
#include "communication/buffer.hpp"
|
||||
#include "communication/context.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
|
||||
@@ -413,6 +413,8 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
||||
|
||||
void OnRead(const boost::system::error_code &ec, const size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
// TODO Check if client disconnected
|
||||
session_.HandleError();
|
||||
return OnError(ec);
|
||||
}
|
||||
input_buffer_.write_end()->Written(bytes_transferred);
|
||||
|
||||
9
src/flags/CMakeLists.txt
Normal file
9
src/flags/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
add_library(mg-flags STATIC audit.cpp
|
||||
bolt.cpp
|
||||
general.cpp
|
||||
isolation_level.cpp
|
||||
log_level.cpp
|
||||
memory_limit.cpp
|
||||
run_time_configurable.cpp)
|
||||
target_include_directories(mg-flags PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(mg-flags PUBLIC spdlog::spdlog mg-settings mg-utils)
|
||||
@@ -16,3 +16,4 @@
|
||||
#include "flags/isolation_level.hpp"
|
||||
#include "flags/log_level.hpp"
|
||||
#include "flags/memory_limit.hpp"
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
|
||||
@@ -36,3 +36,7 @@ DEFINE_VALIDATED_int32(bolt_session_inactivity_timeout, 1800,
|
||||
DEFINE_string(bolt_cert_file, "", "Certificate file which should be used for the Bolt server.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(bolt_key_file, "", "Key file which should be used for the Bolt server.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(bolt_server_name_for_init, "",
|
||||
"Server name which the database should send to the client in the "
|
||||
"Bolt INIT message.");
|
||||
|
||||
@@ -25,3 +25,5 @@ DECLARE_int32(bolt_session_inactivity_timeout);
|
||||
DECLARE_string(bolt_cert_file);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(bolt_key_file);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(bolt_server_name_for_init);
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
|
||||
#include "general.hpp"
|
||||
|
||||
#include "glue/auth_global.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
#include "glue/auth_handler.hpp"
|
||||
#include <thread>
|
||||
|
||||
// Short help flag.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -142,7 +146,7 @@ DEFINE_string(pulsar_service_url, "", "Default URL used while connecting to Puls
|
||||
// Query flags.
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_double(query_execution_timeout_sec, 600,
|
||||
DEFINE_double(query_execution_timeout_sec, -1,
|
||||
"Maximum allowed query execution time. Queries exceeding this "
|
||||
"limit will be aborted. Value of 0 means no limit.");
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ inline constexpr std::array log_level_mappings{
|
||||
const std::string log_level_help_string = fmt::format("Minimum log level. Allowed values: {}",
|
||||
memgraph::utils::GetAllowedEnumValuesString(log_level_mappings));
|
||||
|
||||
DEFINE_VALIDATED_string(log_level, "WARNING", log_level_help_string.c_str(), {
|
||||
DEFINE_VALIDATED_string(log_level, "WARNING", log_level_help_string.c_str(),
|
||||
{ return memgraph::flags::ValidLogLevel(value); });
|
||||
|
||||
bool memgraph::flags::ValidLogLevel(std::string_view value) {
|
||||
if (const auto result = memgraph::utils::IsValidEnumValueString(value, log_level_mappings); result.HasError()) {
|
||||
const auto error = result.GetError();
|
||||
switch (error) {
|
||||
@@ -55,10 +58,14 @@ DEFINE_VALIDATED_string(log_level, "WARNING", log_level_help_string.c_str(), {
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<spdlog::level::level_enum> memgraph::flags::LogLevelToEnum(std::string_view value) {
|
||||
return memgraph::utils::StringToEnum<spdlog::level::level_enum>(value, log_level_mappings);
|
||||
}
|
||||
|
||||
spdlog::level::level_enum ParseLogLevel() {
|
||||
const auto log_level = memgraph::utils::StringToEnum<spdlog::level::level_enum>(FLAGS_log_level, log_level_mappings);
|
||||
const auto log_level = memgraph::flags::LogLevelToEnum(FLAGS_log_level);
|
||||
MG_ASSERT(log_level, "Invalid log level");
|
||||
return *log_level;
|
||||
}
|
||||
@@ -70,14 +77,19 @@ void CreateLoggerFromSink(const auto &sinks, const auto log_level) {
|
||||
logger->set_level(log_level);
|
||||
logger->flush_on(spdlog::level::trace);
|
||||
spdlog::set_default_logger(std::move(logger));
|
||||
// Enable stderr sink
|
||||
if (FLAGS_also_log_to_stderr) {
|
||||
memgraph::flags::LogToStderr(log_level);
|
||||
}
|
||||
}
|
||||
|
||||
void memgraph::flags::InitializeLogger() {
|
||||
std::vector<spdlog::sink_ptr> sinks;
|
||||
|
||||
if (FLAGS_also_log_to_stderr) {
|
||||
sinks.emplace_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
|
||||
}
|
||||
// Force the stderr logger to be at the front of the sinks vector
|
||||
// Will be used to disable/enable it at run-time by settings its log level
|
||||
sinks.emplace_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>());
|
||||
sinks.back()->set_level(spdlog::level::off);
|
||||
|
||||
if (!FLAGS_log_file.empty()) {
|
||||
// get local time
|
||||
@@ -93,9 +105,18 @@ void memgraph::flags::InitializeLogger() {
|
||||
CreateLoggerFromSink(sinks, ParseLogLevel());
|
||||
}
|
||||
|
||||
// TODO: Make sure this is used in a safe way
|
||||
void memgraph::flags::AddLoggerSink(spdlog::sink_ptr new_sink) {
|
||||
auto default_logger = spdlog::default_logger();
|
||||
auto sinks = default_logger->sinks();
|
||||
sinks.push_back(new_sink);
|
||||
CreateLoggerFromSink(sinks, default_logger->level());
|
||||
}
|
||||
|
||||
// Thread-safe because the level enum is an atomic
|
||||
// NOTE: default_logger is not thread-safe and shouldn't be changed during application lifetime
|
||||
void memgraph::flags::LogToStderr(spdlog::level::level_enum log_level) {
|
||||
auto default_logger = spdlog::default_logger();
|
||||
auto sink = default_logger->sinks().front();
|
||||
sink->set_level(log_level);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/sinks/sink.h>
|
||||
#include <optional>
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
DECLARE_string(log_level);
|
||||
DECLARE_bool(also_log_to_stderr);
|
||||
|
||||
namespace memgraph::flags {
|
||||
|
||||
bool ValidLogLevel(std::string_view value);
|
||||
std::optional<spdlog::level::level_enum> LogLevelToEnum(std::string_view value);
|
||||
|
||||
void InitializeLogger();
|
||||
void AddLoggerSink(spdlog::sink_ptr new_sink);
|
||||
void LogToStderr(spdlog::level::level_enum log_level);
|
||||
|
||||
} // namespace memgraph::flags
|
||||
|
||||
111
src/flags/run_time_configurable.cpp
Normal file
111
src/flags/run_time_configurable.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#include <string>
|
||||
#include "flags/bolt.hpp"
|
||||
#include "flags/general.hpp"
|
||||
#include "flags/log_level.hpp"
|
||||
#include "spdlog/cfg/helpers-inl.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace {
|
||||
// Bolt server name
|
||||
constexpr auto kServerNameSettingKey = "server.name";
|
||||
constexpr auto kDefaultServerName = "Neo4j/v5.11.0 compatible graph database server - Memgraph";
|
||||
// Query timeout
|
||||
constexpr auto kQueryTxSettingKey = "query.timeout";
|
||||
constexpr auto kDefaultQueryTx = "600"; // seconds
|
||||
// Log level
|
||||
// No default value because it is not persistent
|
||||
constexpr auto kLogLevelSettingKey = "log.level";
|
||||
// Log to stderr
|
||||
// No default value because it is not persistent
|
||||
constexpr auto kLogToStderrSettingKey = "log.to_stderr";
|
||||
} // namespace
|
||||
|
||||
namespace memgraph::flags::run_time {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
memgraph::utils::Synchronized<std::string, memgraph::utils::SpinLock> bolt_server_name_;
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
std::atomic<double> execution_timeout_sec_;
|
||||
|
||||
void Initialize() {
|
||||
// Register bolt server name settings
|
||||
memgraph::utils::global_settings.RegisterSetting(kServerNameSettingKey, kDefaultServerName, [&] {
|
||||
const auto server_name = memgraph::utils::global_settings.GetValue(kServerNameSettingKey);
|
||||
MG_ASSERT(server_name, "Bolt server name is missing from the settings");
|
||||
*(bolt_server_name_.Lock()) = *server_name;
|
||||
});
|
||||
// Update value from read settings
|
||||
const auto &name = memgraph::utils::global_settings.GetValue(kServerNameSettingKey);
|
||||
MG_ASSERT(name, "Failed to read server name from settings.");
|
||||
*(bolt_server_name_.Lock()) = *name;
|
||||
// Override server name if passed via command line argument
|
||||
if (!FLAGS_bolt_server_name_for_init.empty()) {
|
||||
memgraph::utils::global_settings.SetValue(kServerNameSettingKey, FLAGS_bolt_server_name_for_init);
|
||||
}
|
||||
|
||||
// Register query timeout
|
||||
memgraph::utils::global_settings.RegisterSetting(kQueryTxSettingKey, kDefaultQueryTx, [&] {
|
||||
const auto query_tx = memgraph::utils::global_settings.GetValue(kQueryTxSettingKey);
|
||||
MG_ASSERT(query_tx, "Query timeout is missing from the settings");
|
||||
execution_timeout_sec_ = std::stod(*query_tx);
|
||||
});
|
||||
// Update value from read settings
|
||||
const auto &tx = memgraph::utils::global_settings.GetValue(kQueryTxSettingKey);
|
||||
MG_ASSERT(tx, "Failed to read query timeout from settings.");
|
||||
execution_timeout_sec_ = std::stod(*tx);
|
||||
// Override query timeout if passed via command line argument
|
||||
if (FLAGS_query_execution_timeout_sec != -1) {
|
||||
memgraph::utils::global_settings.SetValue(kQueryTxSettingKey, std::to_string(FLAGS_query_execution_timeout_sec));
|
||||
}
|
||||
|
||||
// Register log level
|
||||
auto get_global_log_level = []() {
|
||||
const auto log_level = memgraph::utils::global_settings.GetValue(kLogLevelSettingKey);
|
||||
MG_ASSERT(log_level, "Log level is missing from the settings");
|
||||
const auto ll_enum = memgraph::flags::LogLevelToEnum(*log_level);
|
||||
if (!ll_enum) {
|
||||
throw utils::BasicException("Unsupported log level {}", *log_level);
|
||||
}
|
||||
return *ll_enum;
|
||||
};
|
||||
memgraph::utils::global_settings.RegisterSetting(
|
||||
kLogLevelSettingKey, FLAGS_log_level, [&] { spdlog::set_level(get_global_log_level()); },
|
||||
memgraph::flags::ValidLogLevel);
|
||||
// Always override log level with command line argument
|
||||
memgraph::utils::global_settings.SetValue(kLogLevelSettingKey, FLAGS_log_level);
|
||||
|
||||
// Register logging to stderr
|
||||
auto bool_to_str = [](bool in) { return in ? "true" : "false"; };
|
||||
const std::string log_to_stderr_s = bool_to_str(FLAGS_also_log_to_stderr);
|
||||
memgraph::utils::global_settings.RegisterSetting(
|
||||
kLogToStderrSettingKey, log_to_stderr_s,
|
||||
[&] {
|
||||
const auto enable = memgraph::utils::global_settings.GetValue(kLogToStderrSettingKey);
|
||||
if (enable == "true") {
|
||||
LogToStderr(get_global_log_level());
|
||||
} else {
|
||||
LogToStderr(spdlog::level::off);
|
||||
}
|
||||
},
|
||||
[](std::string_view in) {
|
||||
const auto lc = memgraph::utils::ToLowerCase(in);
|
||||
return lc == "false" || lc == "true";
|
||||
});
|
||||
// Always override log to stderr with command line argument
|
||||
memgraph::utils::global_settings.SetValue(kLogToStderrSettingKey, log_to_stderr_s);
|
||||
}
|
||||
} // namespace memgraph::flags::run_time
|
||||
26
src/flags/run_time_configurable.hpp
Normal file
26
src/flags/run_time_configurable.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
#pragma once
|
||||
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::flags::run_time {
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern utils::Synchronized<std::string, utils::SpinLock> bolt_server_name_;
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern std::atomic<double> execution_timeout_sec_;
|
||||
|
||||
void Initialize();
|
||||
|
||||
} // namespace memgraph::flags::run_time
|
||||
@@ -10,12 +10,15 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "glue/SessionHL.hpp"
|
||||
#include <optional>
|
||||
|
||||
#include "audit/log.hpp"
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#include "glue/auth_checker.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "query/discard_value_stream.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
@@ -23,11 +26,6 @@ namespace memgraph::metrics {
|
||||
extern const Event ActiveBoltSessions;
|
||||
} // namespace memgraph::metrics
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(bolt_server_name_for_init, "",
|
||||
"Server name which the database should send to the client in the "
|
||||
"Bolt INIT message.");
|
||||
|
||||
auto ToQueryExtras(const memgraph::communication::bolt::Value &extra) -> memgraph::query::QueryExtras {
|
||||
auto const &as_map = extra.ValueMap();
|
||||
|
||||
@@ -151,9 +149,10 @@ memgraph::dbms::SetForResult SessionHL::OnChange(const std::string &db_name) {
|
||||
std::string SessionHL::GetDatabaseName() const { return interpreter_context_->db->id(); }
|
||||
|
||||
std::optional<std::string> SessionHL::GetServerNameForInit() {
|
||||
if (FLAGS_bolt_server_name_for_init.empty()) return std::nullopt;
|
||||
return FLAGS_bolt_server_name_for_init;
|
||||
auto locked_name = flags::run_time::bolt_server_name_.Lock();
|
||||
return locked_name->empty() ? std::nullopt : std::make_optional(*locked_name);
|
||||
}
|
||||
|
||||
bool SessionHL::Authenticate(const std::string &username, const std::string &password) {
|
||||
auto locked_auth = auth_->Lock();
|
||||
if (!locked_auth->HasUsers()) {
|
||||
@@ -304,6 +303,7 @@ SessionHL::SessionHL(
|
||||
#endif
|
||||
endpoint_(endpoint),
|
||||
run_id_(current_.run_id()) {
|
||||
// Metrics update
|
||||
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveBoltSessions);
|
||||
}
|
||||
|
||||
|
||||
16
src/glue/auth_global.hpp
Normal file
16
src/glue/auth_global.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace memgraph::glue {
|
||||
inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
|
||||
} // namespace memgraph::glue
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <regex>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth_global.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "query/interpreter.hpp"
|
||||
@@ -21,8 +22,6 @@
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
|
||||
|
||||
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
std::string name_regex_string_;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#ifndef MG_ENTERPRISE
|
||||
#include "dbms/session_context_handler.hpp"
|
||||
#endif
|
||||
@@ -209,6 +210,7 @@ int main(int argc, char **argv) {
|
||||
// register all runtime settings
|
||||
memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker,
|
||||
memgraph::utils::global_settings);
|
||||
memgraph::flags::run_time::Initialize();
|
||||
|
||||
memgraph::license::global_license_checker.CheckEnvLicense();
|
||||
if (!FLAGS_organization_name.empty() && !FLAGS_license_key.empty()) {
|
||||
@@ -291,7 +293,6 @@ int main(int argc, char **argv) {
|
||||
// Default interpreter configuration
|
||||
memgraph::query::InterpreterConfig interp_config{
|
||||
.query = {.allow_load_csv = FLAGS_allow_load_csv},
|
||||
.execution_timeout_sec = FLAGS_query_execution_timeout_sec,
|
||||
.replication_replica_check_frequency = std::chrono::seconds(FLAGS_replication_replica_check_frequency_sec),
|
||||
.default_kafka_bootstrap_servers = FLAGS_kafka_bootstrap_servers,
|
||||
.default_pulsar_service_url = FLAGS_pulsar_service_url,
|
||||
|
||||
@@ -40,7 +40,18 @@ set(mg_query_sources
|
||||
|
||||
add_library(mg-query STATIC ${mg_query_sources})
|
||||
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(mg-query PUBLIC dl cppitertools Python3::Python mg-integrations-pulsar mg-integrations-kafka mg-storage-v2 mg-license mg-utils mg-kvstore mg-memory mg::csv)
|
||||
target_link_libraries(mg-query PUBLIC dl
|
||||
cppitertools
|
||||
Python3::Python
|
||||
mg-integrations-pulsar
|
||||
mg-integrations-kafka
|
||||
mg-storage-v2
|
||||
mg-license
|
||||
mg-utils
|
||||
mg-kvstore
|
||||
mg-memory
|
||||
mg::csv
|
||||
mg-flags)
|
||||
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
||||
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
|
||||
endif()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -19,8 +19,6 @@ struct InterpreterConfig {
|
||||
bool allow_load_csv{true};
|
||||
} query;
|
||||
|
||||
// The default execution timeout is 10 minutes.
|
||||
double execution_timeout_sec{600.0};
|
||||
// The same as \ref memgraph::storage::replication::ReplicationClientConfig
|
||||
std::chrono::seconds replication_replica_check_frequency{1};
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ struct EvaluationContext {
|
||||
/// All counters generated by `counter` function, mutable because the function
|
||||
/// modifies the values
|
||||
mutable std::unordered_map<std::string, int64_t> counters{};
|
||||
/// Property lookup cache ({symbol: {property_id: property_value, ...}, ...})
|
||||
mutable std::unordered_map<int32_t, std::map<storage::PropertyId, storage::PropertyValue>> property_lookups_cache{};
|
||||
};
|
||||
|
||||
inline std::vector<storage::PropertyId> NamesToProperties(const std::vector<std::string> &property_names,
|
||||
|
||||
@@ -22,31 +22,13 @@
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/storage_mode.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Our communication layer and query engine don't mix
|
||||
// very well on Centos because OpenSSL version available
|
||||
// on Centos 7 include libkrb5 which has brilliant macros
|
||||
// called TRUE and FALSE. For more detailed explanation go
|
||||
// to memgraph.cpp.
|
||||
//
|
||||
// Because of the replication storage now uses some form of
|
||||
// communication so we have some unwanted macros.
|
||||
// This cannot be avoided by simple include orderings so we
|
||||
// simply undefine those macros as we're sure that libkrb5
|
||||
// won't and can't be used anywhere in the query engine.
|
||||
#include "storage/v2/storage.hpp"
|
||||
|
||||
#undef FALSE
|
||||
#undef TRUE
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
#include "storage/v2/storage_mode.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
|
||||
@@ -1186,6 +1186,8 @@ class PropertyLookup : public memgraph::query::Expression {
|
||||
static const utils::TypeInfo kType;
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
enum class EvaluationMode { GET_OWN_PROPERTY, GET_ALL_PROPERTIES };
|
||||
|
||||
PropertyLookup() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
@@ -1200,11 +1202,13 @@ class PropertyLookup : public memgraph::query::Expression {
|
||||
|
||||
memgraph::query::Expression *expression_{nullptr};
|
||||
memgraph::query::PropertyIx property_;
|
||||
memgraph::query::PropertyLookup::EvaluationMode evaluation_mode_{EvaluationMode::GET_OWN_PROPERTY};
|
||||
|
||||
PropertyLookup *Clone(AstStorage *storage) const override {
|
||||
PropertyLookup *object = storage->Create<PropertyLookup>();
|
||||
object->expression_ = expression_ ? expression_->Clone(storage) : nullptr;
|
||||
object->property_ = storage->GetPropertyIx(property_.name);
|
||||
object->evaluation_mode_ = evaluation_mode_;
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
@@ -400,6 +400,29 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PostVisit(MapLiteral &map_literal) {
|
||||
std::unordered_map<int32_t, PropertyLookup *> property_lookups{};
|
||||
|
||||
for (const auto &pair : map_literal.elements_) {
|
||||
if (pair.second->GetTypeInfo() != PropertyLookup::kType) continue;
|
||||
auto *property_lookup = static_cast<PropertyLookup *>(pair.second);
|
||||
if (property_lookup->expression_->GetTypeInfo() != Identifier::kType) continue;
|
||||
|
||||
auto symbol_pos = static_cast<Identifier *>(property_lookup->expression_)->symbol_pos_;
|
||||
try {
|
||||
auto *existing_property_lookup = property_lookups.at(symbol_pos);
|
||||
// If already there (no exception), update the original and current PropertyLookups
|
||||
existing_property_lookup->evaluation_mode_ = PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES;
|
||||
property_lookup->evaluation_mode_ = PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES;
|
||||
} catch (const std::out_of_range &) {
|
||||
// Otherwise, add the PropertyLookup to the map
|
||||
property_lookups.emplace(symbol_pos, property_lookup);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolGenerator::PreVisit(Aggregation &aggr) {
|
||||
auto &scope = scopes_.back();
|
||||
// Check if the aggregation can be used in this context. This check should
|
||||
|
||||
@@ -72,6 +72,8 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
// Expressions
|
||||
ReturnType Visit(Identifier &) override;
|
||||
ReturnType Visit(PrimitiveLiteral &) override { return true; }
|
||||
bool PreVisit(MapLiteral &) override { return true; }
|
||||
bool PostVisit(MapLiteral &) override;
|
||||
ReturnType Visit(ParameterLookup &) override { return true; }
|
||||
bool PreVisit(Aggregation &) override;
|
||||
bool PostVisit(Aggregation &) override;
|
||||
|
||||
@@ -546,9 +546,35 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex:
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueVertex(), property_lookup.property_), ctx_->memory);
|
||||
if (property_lookup.evaluation_mode_ == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES) {
|
||||
auto symbol_pos = static_cast<Identifier *>(property_lookup.expression_)->symbol_pos_;
|
||||
if (!ctx_->property_lookups_cache.contains(symbol_pos)) {
|
||||
ctx_->property_lookups_cache.emplace(symbol_pos, GetAllProperties(expression_result_ptr->ValueVertex()));
|
||||
}
|
||||
|
||||
auto property_id = ctx_->properties[property_lookup.property_.ix];
|
||||
if (ctx_->property_lookups_cache[symbol_pos].contains(property_id)) {
|
||||
return TypedValue(ctx_->property_lookups_cache[symbol_pos][property_id], ctx_->memory);
|
||||
}
|
||||
return TypedValue(ctx_->memory);
|
||||
} else {
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueVertex(), property_lookup.property_), ctx_->memory);
|
||||
}
|
||||
case TypedValue::Type::Edge:
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueEdge(), property_lookup.property_), ctx_->memory);
|
||||
if (property_lookup.evaluation_mode_ == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES) {
|
||||
auto symbol_pos = static_cast<Identifier *>(property_lookup.expression_)->symbol_pos_;
|
||||
if (!ctx_->property_lookups_cache.contains(symbol_pos)) {
|
||||
ctx_->property_lookups_cache.emplace(symbol_pos, GetAllProperties(expression_result_ptr->ValueEdge()));
|
||||
}
|
||||
|
||||
auto property_id = ctx_->properties[property_lookup.property_.ix];
|
||||
if (ctx_->property_lookups_cache[symbol_pos].contains(property_id)) {
|
||||
return TypedValue(ctx_->property_lookups_cache[symbol_pos][property_id], ctx_->memory);
|
||||
}
|
||||
return TypedValue(ctx_->memory);
|
||||
} else {
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueEdge(), property_lookup.property_), ctx_->memory);
|
||||
}
|
||||
case TypedValue::Type::Map: {
|
||||
auto &map = expression_result_ptr->ValueMap();
|
||||
auto found = map.find(property_lookup.property_.name.c_str());
|
||||
@@ -754,7 +780,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
|
||||
TypedValue Visit(MapLiteral &literal) override {
|
||||
TypedValue::TMap result(ctx_->memory);
|
||||
for (const auto &pair : literal.elements_) result.emplace(pair.first.name, pair.second->Accept(*this));
|
||||
for (const auto &pair : literal.elements_) {
|
||||
result.emplace(pair.first.name, pair.second->Accept(*this));
|
||||
}
|
||||
|
||||
ctx_->property_lookups_cache.clear();
|
||||
// TODO Don’t clear the cache if there are remaining MapLiterals with PropertyLookups that read the same properties
|
||||
// from the same variable (symbol & value)
|
||||
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
|
||||
@@ -1048,6 +1081,33 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
private:
|
||||
template <class TRecordAccessor>
|
||||
std::map<storage::PropertyId, storage::PropertyValue> GetAllProperties(const TRecordAccessor &record_accessor) {
|
||||
auto maybe_props = record_accessor.Properties(view_);
|
||||
if (maybe_props.HasError() && maybe_props.GetError() == storage::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_props = record_accessor.Properties(storage::View::NEW);
|
||||
}
|
||||
if (maybe_props.HasError()) {
|
||||
switch (maybe_props.GetError()) {
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to get properties from a deleted object.");
|
||||
case storage::Error::NONEXISTENT_OBJECT:
|
||||
throw query::QueryRuntimeException("Trying to get properties from an object that doesn't exist.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Unexpected error when getting properties.");
|
||||
}
|
||||
}
|
||||
return *maybe_props;
|
||||
}
|
||||
|
||||
template <class TRecordAccessor>
|
||||
storage::PropertyValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
||||
auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "csv/parsing.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/session_context_handler.hpp"
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "memory/memory_control.hpp"
|
||||
@@ -1377,7 +1378,7 @@ Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_
|
||||
auto DetermineTxTimeout(std::optional<int64_t> tx_timeout_ms, InterpreterConfig const &config) -> TxTimeout {
|
||||
using double_seconds = std::chrono::duration<double>;
|
||||
|
||||
auto const global_tx_timeout = double_seconds{config.execution_timeout_sec};
|
||||
auto const global_tx_timeout = double_seconds{flags::run_time::execution_timeout_sec_};
|
||||
auto const valid_global_tx_timeout = global_tx_timeout > double_seconds{0};
|
||||
|
||||
if (tx_timeout_ms) {
|
||||
@@ -2912,17 +2913,37 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
case InfoQuery::InfoType::INDEX:
|
||||
header = {"index type", "label", "property"};
|
||||
handler = [interpreter_context] {
|
||||
const std::string_view label_index_mark{"label"};
|
||||
const std::string_view label_property_index_mark{"label+property"};
|
||||
auto *db = interpreter_context->db.get();
|
||||
auto info = db->ListAllIndices();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(info.label.size() + info.label_property.size());
|
||||
for (const auto &item : info.label) {
|
||||
results.push_back({TypedValue("label"), TypedValue(db->LabelToName(item)), TypedValue()});
|
||||
results.push_back({TypedValue(label_index_mark), TypedValue(db->LabelToName(item)), TypedValue()});
|
||||
}
|
||||
for (const auto &item : info.label_property) {
|
||||
results.push_back({TypedValue("label+property"), TypedValue(db->LabelToName(item.first)),
|
||||
results.push_back({TypedValue(label_property_index_mark), TypedValue(db->LabelToName(item.first)),
|
||||
TypedValue(db->PropertyToName(item.second))});
|
||||
}
|
||||
|
||||
std::sort(results.begin(), results.end(), [&label_index_mark](const auto &record_1, const auto &record_2) {
|
||||
const auto type_1 = record_1[0].ValueString();
|
||||
const auto type_2 = record_2[0].ValueString();
|
||||
|
||||
if (type_1 != type_2) {
|
||||
return type_1 < type_2;
|
||||
}
|
||||
|
||||
const auto label_1 = record_1[1].ValueString();
|
||||
const auto label_2 = record_2[1].ValueString();
|
||||
if (type_1 == label_index_mark || label_1 != label_2) {
|
||||
return label_1 < label_2;
|
||||
}
|
||||
|
||||
return record_1[2].ValueString() < record_2[2].ValueString();
|
||||
});
|
||||
|
||||
return std::pair{results, QueryHandlerResult::NOTHING};
|
||||
};
|
||||
break;
|
||||
@@ -3778,7 +3799,7 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
|
||||
auto trigger_context = original_trigger_context;
|
||||
trigger_context.AdaptForAccessor(&db_accessor);
|
||||
try {
|
||||
trigger.Execute(&db_accessor, &execution_memory, interpreter_context->config.execution_timeout_sec,
|
||||
trigger.Execute(&db_accessor, &execution_memory, flags::run_time::execution_timeout_sec_,
|
||||
&interpreter_context->is_shutting_down, transaction_status, trigger_context,
|
||||
interpreter_context->auth_checker);
|
||||
} catch (const utils::BasicException &exception) {
|
||||
@@ -3885,7 +3906,7 @@ void Interpreter::Commit() {
|
||||
utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize};
|
||||
AdvanceCommand();
|
||||
try {
|
||||
trigger.Execute(&*execution_db_accessor_, &execution_memory, interpreter_context_->config.execution_timeout_sec,
|
||||
trigger.Execute(&*execution_db_accessor_, &execution_memory, flags::run_time::execution_timeout_sec_,
|
||||
&interpreter_context_->is_shutting_down, &transaction_status_, *trigger_context,
|
||||
interpreter_context_->auth_checker);
|
||||
} catch (const utils::BasicException &e) {
|
||||
|
||||
@@ -172,7 +172,10 @@ inline void AbortCheck(ExecutionContext const &context) {
|
||||
|
||||
} // namespace
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define SCOPED_PROFILE_OP(name) ScopedProfile profile{ComputeProfilingKey(this), name, &context};
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define SCOPED_PROFILE_OP_BY_REF(ref) ScopedProfile profile{ComputeProfilingKey(this), ref, &context};
|
||||
|
||||
bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("Once");
|
||||
@@ -352,7 +355,7 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert
|
||||
} // namespace
|
||||
|
||||
bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("CreateExpand");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
if (!input_cursor_->Pull(frame, context)) return false;
|
||||
|
||||
@@ -431,16 +434,17 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec
|
||||
template <class TVerticesFun>
|
||||
class ScanAllCursor : public Cursor {
|
||||
public:
|
||||
explicit ScanAllCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, storage::View view,
|
||||
explicit ScanAllCursor(const ScanAll &self, Symbol output_symbol, UniqueCursorPtr input_cursor, storage::View view,
|
||||
TVerticesFun get_vertices, const char *op_name)
|
||||
: output_symbol_(output_symbol),
|
||||
: self_(self),
|
||||
output_symbol_(output_symbol),
|
||||
input_cursor_(std::move(input_cursor)),
|
||||
view_(view),
|
||||
get_vertices_(std::move(get_vertices)),
|
||||
op_name_(op_name) {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP(op_name_);
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
AbortCheck(context);
|
||||
|
||||
@@ -491,6 +495,7 @@ class ScanAllCursor : public Cursor {
|
||||
}
|
||||
|
||||
private:
|
||||
const ScanAll &self_;
|
||||
const Symbol output_symbol_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
storage::View view_;
|
||||
@@ -513,8 +518,8 @@ UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const {
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAll");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, *this, output_symbol_, input_->MakeCursor(mem),
|
||||
view_, std::move(vertices), "ScanAll");
|
||||
}
|
||||
|
||||
std::vector<Symbol> ScanAll::ModifiedSymbols(const SymbolTable &table) const {
|
||||
@@ -536,8 +541,8 @@ UniqueCursorPtr ScanAllByLabel::MakeCursor(utils::MemoryResource *mem) const {
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_, label_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAllByLabel");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, *this, output_symbol_, input_->MakeCursor(mem),
|
||||
view_, std::move(vertices), "ScanAllByLabel");
|
||||
}
|
||||
|
||||
// TODO(buda): Implement ScanAllByLabelProperty operator to iterate over
|
||||
@@ -601,8 +606,8 @@ UniqueCursorPtr ScanAllByLabelPropertyRange::MakeCursor(utils::MemoryResource *m
|
||||
if (maybe_upper && maybe_upper->value().IsNull()) return std::nullopt;
|
||||
return std::make_optional(db->Vertices(view_, label_, property_, maybe_lower, maybe_upper));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAllByLabelPropertyRange");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(
|
||||
mem, *this, output_symbol_, input_->MakeCursor(mem), view_, std::move(vertices), "ScanAllByLabelPropertyRange");
|
||||
}
|
||||
|
||||
ScanAllByLabelPropertyValue::ScanAllByLabelPropertyValue(const std::shared_ptr<LogicalOperator> &input,
|
||||
@@ -633,8 +638,8 @@ UniqueCursorPtr ScanAllByLabelPropertyValue::MakeCursor(utils::MemoryResource *m
|
||||
}
|
||||
return std::make_optional(db->Vertices(view_, label_, property_, storage::PropertyValue(value)));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAllByLabelPropertyValue");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(
|
||||
mem, *this, output_symbol_, input_->MakeCursor(mem), view_, std::move(vertices), "ScanAllByLabelPropertyValue");
|
||||
}
|
||||
|
||||
ScanAllByLabelProperty::ScanAllByLabelProperty(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol,
|
||||
@@ -651,8 +656,8 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_, label_, property_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAllByLabelProperty");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, *this, output_symbol_, input_->MakeCursor(mem),
|
||||
view_, std::move(vertices), "ScanAllByLabelProperty");
|
||||
}
|
||||
|
||||
ScanAllById::ScanAllById(const std::shared_ptr<LogicalOperator> &input, Symbol output_symbol, Expression *expression,
|
||||
@@ -677,8 +682,8 @@ UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const {
|
||||
if (!maybe_vertex) return std::nullopt;
|
||||
return std::vector<VertexAccessor>{*maybe_vertex};
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAllById");
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, *this, output_symbol_, input_->MakeCursor(mem),
|
||||
view_, std::move(vertices), "ScanAllById");
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -742,7 +747,7 @@ Expand::ExpandCursor::ExpandCursor(const Expand &self, int64_t input_degree, int
|
||||
prev_existing_degree_(existing_node_degree) {}
|
||||
|
||||
bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("Expand");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
// A helper function for expanding a node from an edge.
|
||||
auto pull_node = [this, &frame](const EdgeAccessor &new_edge, EdgeAtom::Direction direction) {
|
||||
@@ -1031,7 +1036,7 @@ class ExpandVariableCursor : public Cursor {
|
||||
: self_(self), input_cursor_(self.input_->MakeCursor(mem)), edges_(mem), edges_it_(mem) {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("ExpandVariable");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
|
||||
storage::View::OLD);
|
||||
@@ -2522,7 +2527,7 @@ Produce::ProduceCursor::ProduceCursor(const Produce &self, utils::MemoryResource
|
||||
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {}
|
||||
|
||||
bool Produce::ProduceCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("Produce");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
if (input_cursor_->Pull(frame, context)) {
|
||||
// Produce should always yield the latest results.
|
||||
@@ -3403,7 +3408,7 @@ class AggregateCursor : public Cursor {
|
||||
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)), aggregation_(mem) {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("Aggregate");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
if (!pulled_all_input_) {
|
||||
ProcessAll(&frame, &context);
|
||||
@@ -3867,7 +3872,7 @@ class OrderByCursor : public Cursor {
|
||||
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)), cache_(mem) {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("OrderBy");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
if (!did_pull_all_) {
|
||||
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
|
||||
@@ -4269,7 +4274,7 @@ Union::UnionCursor::UnionCursor(const Union &self, utils::MemoryResource *mem)
|
||||
: self_(self), left_cursor_(self.left_op_->MakeCursor(mem)), right_cursor_(self.right_op_->MakeCursor(mem)) {}
|
||||
|
||||
bool Union::UnionCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("Union");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
utils::pmr::unordered_map<std::string, TypedValue> results(context.evaluation_context.memory);
|
||||
if (left_cursor_->Pull(frame, context)) {
|
||||
@@ -4343,7 +4348,7 @@ class CartesianCursor : public Cursor {
|
||||
}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("Cartesian");
|
||||
SCOPED_PROFILE_OP_BY_REF(self_);
|
||||
|
||||
if (!cartesian_pull_initialized_) {
|
||||
// Pull all left_op frames.
|
||||
@@ -4632,7 +4637,7 @@ class CallProcedureCursor : public Cursor {
|
||||
}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("CallProcedure");
|
||||
SCOPED_PROFILE_OP_BY_REF(*self_);
|
||||
|
||||
AbortCheck(context);
|
||||
|
||||
@@ -4911,7 +4916,7 @@ class LoadCsvCursor : public Cursor {
|
||||
: self_(self), input_cursor_(self_->input_->MakeCursor(mem)), did_pull_{false} {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("LoadCsv");
|
||||
SCOPED_PROFILE_OP_BY_REF(*self_);
|
||||
|
||||
AbortCheck(context);
|
||||
|
||||
|
||||
@@ -152,12 +152,19 @@ class HierarchicalLogicalOperatorVisitor : public LogicalOperatorCompositeVisito
|
||||
using typename LogicalOperatorLeafVisitor::ReturnType;
|
||||
};
|
||||
|
||||
class NamedLogicalOperator {
|
||||
public:
|
||||
mutable const DbAccessor *dba_{nullptr};
|
||||
virtual std::string ToString() const = 0;
|
||||
};
|
||||
|
||||
/// Base class for logical operators.
|
||||
///
|
||||
/// Each operator describes an operation, which is to be performed on the
|
||||
/// database. Operators are iterated over using a @c Cursor. Various operators
|
||||
/// can serve as inputs to others and thus a sequence of operations is formed.
|
||||
class LogicalOperator : public utils::Visitable<HierarchicalLogicalOperatorVisitor> {
|
||||
class LogicalOperator : public utils::Visitable<HierarchicalLogicalOperatorVisitor>,
|
||||
public memgraph::query::plan::NamedLogicalOperator {
|
||||
public:
|
||||
static const utils::TypeInfo kType;
|
||||
virtual const utils::TypeInfo &GetTypeInfo() const { return kType; }
|
||||
@@ -232,6 +239,8 @@ class LogicalOperator : public utils::Visitable<HierarchicalLogicalOperatorVisit
|
||||
std::vector<std::shared_ptr<LogicalOperator>> loaded_ops;
|
||||
};
|
||||
|
||||
std::string ToString() const override { return GetTypeInfo().name; }
|
||||
|
||||
virtual std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const = 0;
|
||||
};
|
||||
|
||||
@@ -464,6 +473,13 @@ class CreateExpand : public memgraph::query::plan::LogicalOperator {
|
||||
/// if the given node atom refers to an existing node (either matched or created)
|
||||
bool existing_node_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("CreateExpand ({}){}[{}:{}]{}({})", input_symbol_.name(),
|
||||
edge_info_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", edge_info_.symbol.name(),
|
||||
dba_->EdgeTypeToName(edge_info_.edge_type),
|
||||
edge_info_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", node_info_.symbol.name());
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<CreateExpand>();
|
||||
object->node_info_ = node_info_.Clone(storage);
|
||||
@@ -530,6 +546,8 @@ class ScanAll : public memgraph::query::plan::LogicalOperator {
|
||||
/// transaction sees along with their modifications.
|
||||
storage::View view_;
|
||||
|
||||
std::string ToString() const override { return fmt::format("ScanAll ({})", output_symbol_.name()); }
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAll>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -558,6 +576,10 @@ class ScanAllByLabel : public memgraph::query::plan::ScanAll {
|
||||
|
||||
storage::LabelId label_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("ScanAllByLabel ({} :{})", output_symbol_.name(), dba_->LabelToName(label_));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAllByLabel>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -610,6 +632,11 @@ class ScanAllByLabelPropertyRange : public memgraph::query::plan::ScanAll {
|
||||
std::optional<Bound> lower_bound_;
|
||||
std::optional<Bound> upper_bound_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("ScanAllByLabelPropertyRange ({0} :{1} {{{2}}})", output_symbol_.name(),
|
||||
dba_->LabelToName(label_), dba_->PropertyToName(property_));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAllByLabelPropertyRange>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -668,6 +695,11 @@ class ScanAllByLabelPropertyValue : public memgraph::query::plan::ScanAll {
|
||||
std::string property_name_;
|
||||
Expression *expression_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("ScanAllByLabelPropertyValue ({0} :{1} {{{2}}})", output_symbol_.name(),
|
||||
dba_->LabelToName(label_), dba_->PropertyToName(property_));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAllByLabelPropertyValue>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -704,6 +736,11 @@ class ScanAllByLabelProperty : public memgraph::query::plan::ScanAll {
|
||||
std::string property_name_;
|
||||
Expression *expression_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("ScanAllByLabelProperty ({0} :{1} {{{2}}})", output_symbol_.name(), dba_->LabelToName(label_),
|
||||
dba_->PropertyToName(property_));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAllByLabelProperty>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -732,6 +769,8 @@ class ScanAllById : public memgraph::query::plan::ScanAll {
|
||||
|
||||
Expression *expression_;
|
||||
|
||||
std::string ToString() const override { return fmt::format("ScanAllById ({})", output_symbol_.name()); }
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ScanAllById>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -848,6 +887,15 @@ class Expand : public memgraph::query::plan::LogicalOperator {
|
||||
/// State from which the input node should get expanded.
|
||||
storage::View view_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format(
|
||||
"Expand ({}){}[{}{}]{}({})", input_symbol_.name(),
|
||||
common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", common_.edge_symbol.name(),
|
||||
utils::IterableToString(common_.edge_types, "|",
|
||||
[this](const auto &edge_type) { return ":" + dba_->EdgeTypeToName(edge_type); }),
|
||||
common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", common_.node_symbol.name());
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<Expand>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -955,6 +1003,37 @@ class ExpandVariable : public memgraph::query::plan::LogicalOperator {
|
||||
std::optional<memgraph::query::plan::ExpansionLambda> weight_lambda_;
|
||||
std::optional<Symbol> total_weight_;
|
||||
|
||||
std::string OperatorName() const {
|
||||
using Type = query::EdgeAtom::Type;
|
||||
switch (type_) {
|
||||
case Type::DEPTH_FIRST:
|
||||
return "ExpandVariable";
|
||||
break;
|
||||
case Type::BREADTH_FIRST:
|
||||
return (common_.existing_node ? "STShortestPath" : "BFSExpand");
|
||||
break;
|
||||
case Type::WEIGHTED_SHORTEST_PATH:
|
||||
return "WeightedShortestPath";
|
||||
break;
|
||||
case Type::ALL_SHORTEST_PATHS:
|
||||
return "AllShortestPaths";
|
||||
break;
|
||||
case Type::SINGLE:
|
||||
LOG_FATAL("Unexpected ExpandVariable::type_");
|
||||
default:
|
||||
LOG_FATAL("Unexpected ExpandVariable::type_");
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format(
|
||||
"{} ({}){}[{}{}]{}({})", OperatorName(), input_symbol_.name(),
|
||||
common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-", common_.edge_symbol.name(),
|
||||
utils::IterableToString(common_.edge_types, "|",
|
||||
[this](const auto &edge_type) { return ":" + dba_->EdgeTypeToName(edge_type); }),
|
||||
common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-", common_.node_symbol.name());
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<ExpandVariable>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -1097,6 +1176,11 @@ class Produce : public memgraph::query::plan::LogicalOperator {
|
||||
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_;
|
||||
std::vector<NamedExpression *> named_expressions_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("Produce {{{}}}", utils::IterableToString(named_expressions_, ", ",
|
||||
[](const auto &nexpr) { return nexpr->name_; }));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<Produce>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -1628,6 +1712,13 @@ class Aggregate : public memgraph::query::plan::LogicalOperator {
|
||||
std::vector<Expression *> group_by_;
|
||||
std::vector<Symbol> remember_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format(
|
||||
"Aggregate {{{0}}} {{{1}}}",
|
||||
utils::IterableToString(aggregations_, ", ", [](const auto &aggr) { return aggr.output_sym.name(); }),
|
||||
utils::IterableToString(remember_, ", ", [](const auto &sym) { return sym.name(); }));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<Aggregate>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -1833,6 +1924,11 @@ class OrderBy : public memgraph::query::plan::LogicalOperator {
|
||||
std::vector<Expression *> order_by_;
|
||||
std::vector<Symbol> output_symbols_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("OrderBy {{{}}}",
|
||||
utils::IterableToString(output_symbols_, ", ", [](const auto &sym) { return sym.name(); }));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<OrderBy>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -2064,6 +2160,12 @@ class Union : public memgraph::query::plan::LogicalOperator {
|
||||
std::vector<Symbol> left_symbols_;
|
||||
std::vector<Symbol> right_symbols_;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("Union {{{0} : {1}}}",
|
||||
utils::IterableToString(left_symbols_, ", ", [](const auto &sym) { return sym.name(); }),
|
||||
utils::IterableToString(right_symbols_, ", ", [](const auto &sym) { return sym.name(); }));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<Union>();
|
||||
object->left_op_ = left_op_ ? left_op_->Clone(storage) : nullptr;
|
||||
@@ -2226,6 +2328,11 @@ class CallProcedure : public memgraph::query::plan::LogicalOperator {
|
||||
mutable utils::MonotonicBufferResource monotonic_memory{1024UL * 1024UL};
|
||||
utils::MemoryResource *memory_resource = &monotonic_memory;
|
||||
|
||||
std::string ToString() const override {
|
||||
return fmt::format("CallProcedure<{0}> {{{1}}}", procedure_name_,
|
||||
utils::IterableToString(result_symbols_, ", ", [](const auto &sym) { return sym.name(); }));
|
||||
}
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<CallProcedure>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
@@ -2273,6 +2380,8 @@ class LoadCsv : public memgraph::query::plan::LogicalOperator {
|
||||
Expression *nullif_{nullptr};
|
||||
Symbol row_var_;
|
||||
|
||||
std::string ToString() const override { return fmt::format("LoadCsv {{{}}}", row_var_.name()); }
|
||||
|
||||
std::unique_ptr<LogicalOperator> Clone(AstStorage *storage) const override {
|
||||
auto object = std::make_unique<LoadCsv>();
|
||||
object->input_ = input_ ? input_->Clone(storage) : nullptr;
|
||||
|
||||
@@ -30,121 +30,68 @@ PlanPrinter::PlanPrinter(const DbAccessor *dba, std::ostream *out) : dba_(dba),
|
||||
PRE_VISIT(CreateNode);
|
||||
|
||||
bool PlanPrinter::PreVisit(CreateExpand &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* CreateExpand (" << op.input_symbol_.name() << ")"
|
||||
<< (op.edge_info_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.edge_info_.symbol.name() << ":" << dba_->EdgeTypeToName(op.edge_info_.edge_type) << "]"
|
||||
<< (op.edge_info_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.node_info_.symbol.name() << ")";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
PRE_VISIT(Delete);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ScanAll &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAll"
|
||||
<< " (" << op.output_symbol_.name() << ")";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ScanAllByLabel &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabel"
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << ")";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelPropertyValue &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyValue"
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelPropertyRange &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelPropertyRange"
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ScanAllByLabelProperty &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllByLabelProperty"
|
||||
<< " (" << op.output_symbol_.name() << " :" << dba_->LabelToName(op.label_) << " {"
|
||||
<< dba_->PropertyToName(op.property_) << "})";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(ScanAllById &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* ScanAllById"
|
||||
<< " (" << op.output_symbol_.name() << ")";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Expand &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
*out_ << "* Expand (" << op.input_symbol_.name() << ")"
|
||||
<< (op.common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.common_.edge_symbol.name();
|
||||
utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) {
|
||||
stream << ":" << dba_->EdgeTypeToName(edge_type);
|
||||
});
|
||||
*out_ << "]" << (op.common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.common_.node_symbol.name() << ")";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::ExpandVariable &op) {
|
||||
using Type = query::EdgeAtom::Type;
|
||||
WithPrintLn([&](auto &out) {
|
||||
*out_ << "* ";
|
||||
switch (op.type_) {
|
||||
case Type::DEPTH_FIRST:
|
||||
*out_ << "ExpandVariable";
|
||||
break;
|
||||
case Type::BREADTH_FIRST:
|
||||
*out_ << (op.common_.existing_node ? "STShortestPath" : "BFSExpand");
|
||||
break;
|
||||
case Type::WEIGHTED_SHORTEST_PATH:
|
||||
*out_ << "WeightedShortestPath";
|
||||
break;
|
||||
case Type::ALL_SHORTEST_PATHS:
|
||||
*out_ << "AllShortestPaths";
|
||||
break;
|
||||
case Type::SINGLE:
|
||||
LOG_FATAL("Unexpected ExpandVariable::type_");
|
||||
}
|
||||
*out_ << " (" << op.input_symbol_.name() << ")"
|
||||
<< (op.common_.direction == query::EdgeAtom::Direction::IN ? "<-" : "-") << "["
|
||||
<< op.common_.edge_symbol.name();
|
||||
utils::PrintIterable(*out_, op.common_.edge_types, "|", [this](auto &stream, const auto &edge_type) {
|
||||
stream << ":" << dba_->EdgeTypeToName(edge_type);
|
||||
});
|
||||
*out_ << "]" << (op.common_.direction == query::EdgeAtom::Direction::OUT ? "->" : "-") << "("
|
||||
<< op.common_.node_symbol.name() << ")";
|
||||
});
|
||||
op.dba_ = dba_;
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
op.dba_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Produce &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Produce {";
|
||||
utils::PrintIterable(out, op.named_expressions_, ", ", [](auto &out, const auto &nexpr) { out << nexpr->name_; });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,14 +107,7 @@ PRE_VISIT(EmptyResult);
|
||||
PRE_VISIT(EvaluatePatternFilter);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
out << "* Aggregate {";
|
||||
utils::PrintIterable(out, op.aggregations_, ", ",
|
||||
[](auto &out, const auto &aggr) { out << aggr.output_sym.name(); });
|
||||
out << "} {";
|
||||
utils::PrintIterable(out, op.remember_, ", ", [](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,11 +115,7 @@ PRE_VISIT(Skip);
|
||||
PRE_VISIT(Limit);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::OrderBy &op) {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* OrderBy {";
|
||||
utils::PrintIterable(out, op.output_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -202,29 +138,19 @@ PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Union &op) {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* Union {";
|
||||
utils::PrintIterable(out, op.left_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << " : ";
|
||||
utils::PrintIterable(out, op.right_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
Branch(*op.right_op_);
|
||||
op.left_op_->Accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::CallProcedure &op) {
|
||||
WithPrintLn([&op](auto &out) {
|
||||
out << "* CallProcedure<" << op.procedure_name_ << "> {";
|
||||
utils::PrintIterable(out, op.result_symbols_, ", ", [](auto &out, const auto &sym) { out << sym.name(); });
|
||||
out << "}";
|
||||
});
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::LoadCsv &op) {
|
||||
WithPrintLn([&op](auto &out) { out << "* LoadCsv {" << op.row_var_.name() << "}"; });
|
||||
WithPrintLn([&](auto &out) { out << "* " << op.ToString(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -57,7 +57,7 @@ class ProfilingStatsToTableHelper {
|
||||
auto cycles = IndividualCycles(cumulative_stats);
|
||||
|
||||
rows_.emplace_back(std::vector<TypedValue>{
|
||||
TypedValue(FormatOperator(cumulative_stats.name)), TypedValue(cumulative_stats.actual_hits),
|
||||
TypedValue(FormatOperator(cumulative_stats.name.c_str())), TypedValue(cumulative_stats.actual_hits),
|
||||
TypedValue(FormatRelativeTime(cycles)), TypedValue(FormatAbsoluteTime(cycles))});
|
||||
|
||||
for (size_t i = 1; i < cumulative_stats.children.size(); ++i) {
|
||||
@@ -137,7 +137,7 @@ class ProfilingStatsToJsonHelper {
|
||||
void Output(const ProfilingStats &cumulative_stats, json *obj) {
|
||||
auto cycles = IndividualCycles(cumulative_stats);
|
||||
|
||||
obj->emplace("name", cumulative_stats.name);
|
||||
obj->emplace("name", cumulative_stats.name.c_str());
|
||||
obj->emplace("actual_hits", cumulative_stats.actual_hits);
|
||||
obj->emplace("relative_time", RelativeTime(cycles, total_cycles_));
|
||||
obj->emplace("absolute_time", AbsoluteTime(cycles, total_cycles_, total_time_));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -29,7 +29,7 @@ struct ProfilingStats {
|
||||
int64_t actual_hits{0};
|
||||
unsigned long long num_cycles{0};
|
||||
uint64_t key{0};
|
||||
const char *name{nullptr};
|
||||
std::string name;
|
||||
// TODO: This should use the allocator for query execution
|
||||
std::vector<ProfilingStats> children;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -28,6 +28,43 @@ namespace memgraph::query::plan {
|
||||
*/
|
||||
class ScopedProfile {
|
||||
public:
|
||||
ScopedProfile(uint64_t key, const query::plan::NamedLogicalOperator &op, query::ExecutionContext *context) noexcept
|
||||
: context_(context) {
|
||||
if (UNLIKELY(context_->is_profile_query)) {
|
||||
root_ = context_->stats_root;
|
||||
|
||||
// Are we the root logical operator?
|
||||
if (!root_) {
|
||||
stats_ = &context_->stats;
|
||||
stats_->key = key;
|
||||
op.dba_ = context->db_accessor;
|
||||
stats_->name = op.ToString();
|
||||
op.dba_ = nullptr;
|
||||
} else {
|
||||
stats_ = nullptr;
|
||||
|
||||
// Was this logical operator already hit on one of the previous pulls?
|
||||
auto it = std::find_if(root_->children.begin(), root_->children.end(),
|
||||
[key](auto &stats) { return stats.key == key; });
|
||||
|
||||
if (it == root_->children.end()) {
|
||||
root_->children.emplace_back();
|
||||
stats_ = &root_->children.back();
|
||||
stats_->key = key;
|
||||
op.dba_ = context->db_accessor;
|
||||
stats_->name = op.ToString();
|
||||
op.dba_ = nullptr;
|
||||
} else {
|
||||
stats_ = &(*it);
|
||||
}
|
||||
}
|
||||
|
||||
context_->stats_root = stats_;
|
||||
stats_->actual_hits++;
|
||||
start_time_ = utils::ReadTSC();
|
||||
}
|
||||
}
|
||||
|
||||
ScopedProfile(uint64_t key, const char *name, query::ExecutionContext *context) noexcept : context_(context) {
|
||||
if (UNLIKELY(context_->is_profile_query)) {
|
||||
root_ = context_->stats_root;
|
||||
|
||||
@@ -8,6 +8,6 @@ find_package(gflags REQUIRED)
|
||||
|
||||
add_library(mg-requests STATIC ${requests_src_files})
|
||||
target_link_libraries(mg-requests
|
||||
PUBLIC mg-utils spdlog::spdlog fmt::fmt gflags json ${CURL_LIBRARIES}
|
||||
PRIVATE lib::ctre)
|
||||
PUBLIC mg-utils lib::json
|
||||
PRIVATE lib::ctre spdlog::spdlog ${CURL_LIBRARIES} fmt::fmt gflags)
|
||||
target_include_directories(mg-requests PRIVATE ${CURL_INCLUDE_DIRS})
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <json/json.hpp>
|
||||
|
||||
namespace memgraph::requests {
|
||||
|
||||
@@ -32,7 +32,6 @@ add_library(mg-storage-v2 STATIC
|
||||
disk/label_property_index.cpp
|
||||
disk/unique_constraints.cpp
|
||||
storage_mode.cpp
|
||||
isolation_level.cpp
|
||||
replication/replication_client.cpp
|
||||
replication/replication_server.cpp
|
||||
replication/serialization.cpp
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "isolation_level.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
std::string_view IsolationLevelToString(IsolationLevel isolation_level) {
|
||||
switch (isolation_level) {
|
||||
case IsolationLevel::READ_COMMITTED:
|
||||
return "READ_COMMITTED";
|
||||
case IsolationLevel::READ_UNCOMMITTED:
|
||||
return "READ_UNCOMMITTED";
|
||||
case IsolationLevel::SNAPSHOT_ISOLATION:
|
||||
return "SNAPSHOT_ISOLATION";
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view IsolationLevelToString(std::optional<IsolationLevel> isolation_level) {
|
||||
if (isolation_level) {
|
||||
return IsolationLevelToString(*isolation_level);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
@@ -19,7 +19,22 @@ namespace memgraph::storage {
|
||||
|
||||
enum class IsolationLevel : std::uint8_t { SNAPSHOT_ISOLATION, READ_COMMITTED, READ_UNCOMMITTED };
|
||||
|
||||
std::string_view IsolationLevelToString(IsolationLevel isolation_level);
|
||||
std::string_view IsolationLevelToString(std::optional<IsolationLevel> isolation_level);
|
||||
static inline std::string_view IsolationLevelToString(IsolationLevel isolation_level) {
|
||||
switch (isolation_level) {
|
||||
case IsolationLevel::READ_COMMITTED:
|
||||
return "READ_COMMITTED";
|
||||
case IsolationLevel::READ_UNCOMMITTED:
|
||||
return "READ_UNCOMMITTED";
|
||||
case IsolationLevel::SNAPSHOT_ISOLATION:
|
||||
return "SNAPSHOT_ISOLATION";
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string_view IsolationLevelToString(std::optional<IsolationLevel> isolation_level) {
|
||||
if (isolation_level) {
|
||||
return IsolationLevelToString(*isolation_level);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "storage/v2/delta.hpp"
|
||||
#include "storage/v2/durability/storage_global_operation.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
/// REPLICATION ///
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "storage/v2/replication/replication_server.hpp"
|
||||
#include "storage/v2/storage_error.hpp"
|
||||
#include "storage/v2/storage_mode.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertices_iterable.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/event_histogram.hpp"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -22,6 +22,55 @@
|
||||
|
||||
namespace memgraph::utils {
|
||||
|
||||
/**
|
||||
* Outputs a collection of items as a string, separating them with the given delimiter.
|
||||
*
|
||||
* @param first Starting iterator of collection which items are going to be
|
||||
* printed.
|
||||
* @param last Ending iterator of the collection.
|
||||
* @param delim Delimiter that is put between items.
|
||||
* @param transformation Function which accepts an item and returns a derived value.
|
||||
*/
|
||||
template <typename TIterator, typename TTransformation>
|
||||
inline std::string IterableToString(TIterator first, TIterator last, const std::string_view delim = ", ",
|
||||
TTransformation transformation = {}) {
|
||||
std::string representation;
|
||||
if (first != last) {
|
||||
representation.append(transformation(*first));
|
||||
++first;
|
||||
}
|
||||
for (; first != last; ++first) {
|
||||
representation.append(delim);
|
||||
representation.append(transformation(*first));
|
||||
}
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a collection of items as a string, separating them with the given delimiter.
|
||||
*
|
||||
* @param iterable An iterable collection of items.
|
||||
* @param delim Delimiter that is put between items.
|
||||
* @param transformation Function which accepts an item and returns a derived value.
|
||||
*/
|
||||
template <typename TIterable, typename TTransformation>
|
||||
inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ",
|
||||
TTransformation transformation = {}) {
|
||||
return IterableToString(iterable.begin(), iterable.end(), delim, transformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a collection of items as a string, separating them with the given delimiter.
|
||||
*
|
||||
* @param iterable An iterable collection of items.
|
||||
* @param delim Delimiter that is put between items.
|
||||
*/
|
||||
template <typename TIterable>
|
||||
inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ") {
|
||||
return IterableToString(iterable, delim, [](const auto &item) { return item; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a collection of items to the given stream, separating them with the
|
||||
* given delimiter.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -29,18 +29,25 @@ void Settings::Finalize() {
|
||||
on_change_callbacks_.clear();
|
||||
}
|
||||
|
||||
void Settings::RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback) {
|
||||
void Settings::RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback,
|
||||
Validation validation) {
|
||||
std::lock_guard settings_guard{settings_lock_};
|
||||
MG_ASSERT(storage_);
|
||||
MG_ASSERT(validation(default_value), "\"{}\"'s default value does not satisfy the validation condition.", name);
|
||||
|
||||
if (const auto maybe_value = storage_->Get(name); maybe_value) {
|
||||
SPDLOG_INFO("The setting with name {} already exists!", name);
|
||||
} else {
|
||||
MG_ASSERT(storage_->Put(name, default_value), "Failed to register a setting");
|
||||
}
|
||||
|
||||
const auto [it, inserted] = on_change_callbacks_.emplace(std::move(name), callback);
|
||||
MG_ASSERT(inserted, "Settings storage is out of sync");
|
||||
{
|
||||
const auto [_, inserted] = on_change_callbacks_.emplace(name, callback);
|
||||
MG_ASSERT(inserted, "Settings storage is out of sync");
|
||||
}
|
||||
{
|
||||
const auto [_, inserted] = validations_.emplace(std::move(name), validation);
|
||||
MG_ASSERT(inserted, "Settings storage is out of sync");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> Settings::GetValue(const std::string &setting_name) const {
|
||||
@@ -59,6 +66,12 @@ bool Settings::SetValue(const std::string &setting_name, const std::string &new_
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto val = validations_.find(setting_name);
|
||||
MG_ASSERT(val != validations_.end(), "Settings storage is out of sync");
|
||||
if (!val->second(new_value)) {
|
||||
throw utils::BasicException("'{}' not valid for '{}'", new_value, setting_name);
|
||||
}
|
||||
|
||||
MG_ASSERT(storage_->Put(setting_name, new_value), "Failed to modify the setting");
|
||||
|
||||
const auto it = on_change_callbacks_.find(setting_name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -22,12 +22,15 @@
|
||||
namespace memgraph::utils {
|
||||
struct Settings {
|
||||
using OnChangeCallback = std::function<void()>;
|
||||
using Validation = std::function<bool(std::string_view)>;
|
||||
|
||||
void Initialize(std::filesystem::path storage_path);
|
||||
// RocksDB depends on statically allocated objects so we need to delete it before the static destruction kicks in
|
||||
void Finalize();
|
||||
|
||||
void RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback);
|
||||
void RegisterSetting(
|
||||
std::string name, const std::string &default_value, OnChangeCallback callback,
|
||||
Validation validation = [](auto) { return true; });
|
||||
std::optional<std::string> GetValue(const std::string &setting_name) const;
|
||||
bool SetValue(const std::string &setting_name, const std::string &new_value);
|
||||
std::vector<std::pair<std::string, std::string>> AllSettings() const;
|
||||
@@ -35,6 +38,7 @@ struct Settings {
|
||||
private:
|
||||
mutable utils::RWLock settings_lock_{RWLock::Priority::WRITE};
|
||||
std::unordered_map<std::string, OnChangeCallback> on_change_callbacks_;
|
||||
std::unordered_map<std::string, Validation> validations_;
|
||||
std::optional<kvstore::KVStore> storage_;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,3 +25,4 @@ python3 docs_how_to_query.py || exit 1
|
||||
python3 max_query_length.py || exit 1
|
||||
python3 transactions.py || exit 1
|
||||
python3 path.py || exit 1
|
||||
python3 server_name.py || exit 1
|
||||
|
||||
52
tests/drivers/python/v5_8/server_name.py
Normal file
52
tests/drivers/python/v5_8/server_name.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2021 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.
|
||||
|
||||
from neo4j import GraphDatabase, basic_auth
|
||||
from neo4j.exceptions import ClientError, TransientError
|
||||
|
||||
|
||||
def get_server_name(tx):
|
||||
res = tx.run("SHOW DATABASE SETTINGS").values()
|
||||
for setting in res:
|
||||
if setting[0] == "server.name":
|
||||
return setting[1]
|
||||
assert False, "No setting named server.name"
|
||||
|
||||
|
||||
def set_server_name(tx, name):
|
||||
tx.run("SET DATABASE SETTING 'server.name' TO '{}'".format(name)).consume()
|
||||
|
||||
|
||||
# Connect, check name, set a new name and recheck
|
||||
with GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False) as driver:
|
||||
with driver.session() as session:
|
||||
default_name = get_server_name(session)
|
||||
assert driver.get_server_info().agent == default_name, "Wrong server name! Expected {} and got {}".format(
|
||||
default_name, driver.get_server_info().agent
|
||||
)
|
||||
|
||||
with driver.session() as session:
|
||||
set_server_name(session, "Neo4j/1.1 compatible database")
|
||||
|
||||
|
||||
with GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False) as driver:
|
||||
assert (
|
||||
driver.get_server_info().agent == "Neo4j/1.1 compatible database"
|
||||
), 'Wrong server name! Expected "Neo4j/1.1 compatible database" and got {}'.format(driver.get_server_info().agent)
|
||||
|
||||
with driver.session() as session:
|
||||
set_server_name(session, default_name)
|
||||
|
||||
|
||||
print("All ok!")
|
||||
@@ -12,6 +12,8 @@
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import time
|
||||
|
||||
from neo4j import GraphDatabase, basic_auth
|
||||
from neo4j.exceptions import ClientError, TransientError
|
||||
|
||||
@@ -35,6 +37,44 @@ def tx_too_long(tx):
|
||||
tx.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt")
|
||||
|
||||
|
||||
def assert_timeout(set_timeout, measure_timeout):
|
||||
print(measure_timeout)
|
||||
print(set_timeout)
|
||||
assert (
|
||||
measure_timeout >= set_timeout and measure_timeout < set_timeout * 1.2
|
||||
), "Wrong timeout; expected {}s and measured {}s".format(set_timeout, measure_timeout)
|
||||
|
||||
|
||||
def get_timeout(tx):
|
||||
res = tx.run("SHOW DATABASE SETTINGS").values()
|
||||
for setting in res:
|
||||
if setting[0] == "query.timeout":
|
||||
return float(setting[1])
|
||||
assert False, "No setting named query.timeout"
|
||||
|
||||
|
||||
def set_timeout(tx, timeout):
|
||||
tx.run("SET DATABASE SETTING 'query.timeout' TO '{}'".format(timeout)).consume()
|
||||
|
||||
|
||||
def test_timeout(driver, set_timeout):
|
||||
# Query that will run for a very long time, transient error expected.
|
||||
timed_out = False
|
||||
try:
|
||||
with driver.session() as session:
|
||||
start_time = time.time()
|
||||
session.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt").consume()
|
||||
except TransientError:
|
||||
end_time = time.time()
|
||||
assert_timeout(set_timeout, end_time - start_time)
|
||||
timed_out = True
|
||||
|
||||
if timed_out:
|
||||
print("The query timed out as was expected.")
|
||||
else:
|
||||
raise Exception("The query should have timed out, but it didn't!")
|
||||
|
||||
|
||||
with GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False) as driver:
|
||||
|
||||
def add_person(f, name, name2):
|
||||
@@ -53,17 +93,16 @@ with GraphDatabase.driver("bolt://localhost:7687", auth=None, encrypted=False) a
|
||||
with driver.session() as session:
|
||||
session.run("UNWIND range(1, 100000) AS x CREATE ()").consume()
|
||||
|
||||
# Query that will run for a very long time, transient error expected.
|
||||
timed_out = False
|
||||
try:
|
||||
with driver.session() as session:
|
||||
session.run("MATCH (a), (b), (c), (d), (e), (f) RETURN COUNT(*) AS cnt").consume()
|
||||
except TransientError:
|
||||
timed_out = True
|
||||
# Test changing the timeout at run-time
|
||||
with driver.session() as session:
|
||||
default_timeout = get_timeout(session)
|
||||
test_timeout(driver, default_timeout)
|
||||
|
||||
if timed_out:
|
||||
print("The query timed out as was expected.")
|
||||
else:
|
||||
raise Exception("The query should have timed out, but it didn't!")
|
||||
with driver.session() as session:
|
||||
set_timeout(session, 1)
|
||||
test_timeout(driver, 1)
|
||||
|
||||
with driver.session() as session:
|
||||
set_timeout(session, default_timeout)
|
||||
|
||||
print("All ok!")
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Old v1 tests
|
||||
run_v1.sh
|
||||
|
||||
# New tests
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
|
||||
@@ -30,7 +34,6 @@ $binary_dir/memgraph \
|
||||
--query-execution-timeout-sec=5 \
|
||||
--bolt-session-inactivity-timeout=10 \
|
||||
--bolt-cert-file="" \
|
||||
--bolt-server-name-for-init="Neo4j/1.1" \
|
||||
--log-file=$tmpdir/logs/memgarph.log \
|
||||
--also-log-to-stderr \
|
||||
--log-level ERROR &
|
||||
@@ -45,7 +48,8 @@ for i in *; do
|
||||
echo "Running: $i"
|
||||
# run all versions
|
||||
for v in *; do
|
||||
if [ ! -d $v ]; then continue; fi
|
||||
#skip v1 (needs different server name)
|
||||
if [[ "$v" == "v1" || ! -d "$v" ]]; then continue; fi
|
||||
pushd $v
|
||||
echo "Running version: $v"
|
||||
./run.sh
|
||||
|
||||
84
tests/drivers/run_v1.sh
Executable file
84
tests/drivers/run_v1.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
|
||||
function wait_for_server {
|
||||
port=$1
|
||||
while ! nc -z -w 1 127.0.0.1 $port; do
|
||||
sleep 0.1
|
||||
done
|
||||
sleep 1
|
||||
}
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd "$DIR"
|
||||
|
||||
# Create a temporary directory.
|
||||
tmpdir=/tmp/memgraph_drivers
|
||||
if [ -d $tmpdir ]; then
|
||||
rm -rf $tmpdir
|
||||
fi
|
||||
mkdir -p $tmpdir
|
||||
|
||||
# Find memgraph binaries.
|
||||
binary_dir="$DIR/../../build"
|
||||
|
||||
# Start memgraph.
|
||||
$binary_dir/memgraph \
|
||||
--data-directory=$tmpdir \
|
||||
--query-execution-timeout-sec=5 \
|
||||
--bolt-session-inactivity-timeout=10 \
|
||||
--bolt-server-name-for-init="Neo4j/1.1" \
|
||||
--bolt-cert-file="" \
|
||||
--log-file=$tmpdir/logs/memgarph.log \
|
||||
--also-log-to-stderr \
|
||||
--log-level ERROR &
|
||||
pid=$!
|
||||
wait_for_server 7687
|
||||
|
||||
# Run all available tests
|
||||
code_test=0
|
||||
for i in *; do
|
||||
if [ ! -d $i ]; then continue; fi
|
||||
pushd $i
|
||||
echo "Running: $i"
|
||||
# run all versions
|
||||
for v in *; do
|
||||
# Run only v1
|
||||
if [[ "$v" != "v1" || ! -d "$v" ]]; then continue; fi
|
||||
pushd $v
|
||||
echo "Running version: $v"
|
||||
./run.sh
|
||||
code_test=$?
|
||||
if [ $code_test -ne 0 ]; then
|
||||
echo "FAILED: $i"
|
||||
break
|
||||
fi
|
||||
popd
|
||||
done;
|
||||
echo
|
||||
popd
|
||||
done
|
||||
|
||||
# Stop memgraph.
|
||||
kill $pid
|
||||
wait $pid
|
||||
code_mg=$?
|
||||
|
||||
# Temporary directory cleanup.
|
||||
if [ -d $tmpdir ]; then
|
||||
rm -rf $tmpdir
|
||||
fi
|
||||
|
||||
# Check memgraph exit code.
|
||||
if [ $code_mg -ne 0 ]; then
|
||||
echo "The memgraph process didn't terminate properly!"
|
||||
exit $code_mg
|
||||
fi
|
||||
|
||||
# Check test exit code.
|
||||
if [ $code_test -ne 0 ]; then
|
||||
echo "One of the tests failed!"
|
||||
exit $code_test
|
||||
fi
|
||||
@@ -63,6 +63,7 @@ add_subdirectory(analytical_mode)
|
||||
add_subdirectory(batched_procedures)
|
||||
add_subdirectory(import_mode)
|
||||
add_subdirectory(concurrent_query_modules)
|
||||
add_subdirectory(show_index_info)
|
||||
add_subdirectory(set_properties)
|
||||
add_subdirectory(transaction_rollback)
|
||||
|
||||
|
||||
@@ -117,8 +117,8 @@ startup_config_dict = {
|
||||
"password_encryption_algorithm": ("bcrypt", "bcrypt", "The password encryption algorithm used for authentication."),
|
||||
"pulsar_service_url": ("", "", "Default URL used while connecting to Pulsar brokers."),
|
||||
"query_execution_timeout_sec": (
|
||||
"600",
|
||||
"600",
|
||||
"-1",
|
||||
"-1",
|
||||
"Maximum allowed query execution time. Queries exceeding this limit will be aborted. Value of 0 means no limit.",
|
||||
),
|
||||
"query_modules_directory": (
|
||||
|
||||
14
tests/e2e/dependency_check.sh
Executable file
14
tests/e2e/dependency_check.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
check_service_in_use() {
|
||||
if docker ps --format "{{.Names}}" | grep -q "$1"; then
|
||||
echo "$2 is successfully working"
|
||||
else
|
||||
echo "$2 needs to be available"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_service_in_use "kafka" "Kafka service"
|
||||
check_service_in_use "pulsar" "Pulsar service"
|
||||
6
tests/e2e/show_index_info/CMakeLists.txt
Normal file
6
tests/e2e/show_index_info/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
function(copy_show_index_info_e2e_python_files FILE_NAME)
|
||||
copy_e2e_python_files(show_index_info ${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
copy_show_index_info_e2e_python_files(common.py)
|
||||
copy_show_index_info_e2e_python_files(test_show_index_info.py)
|
||||
56
tests/e2e/show_index_info/common.py
Normal file
56
tests/e2e/show_index_info/common.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Copyright 2023 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import typing
|
||||
from enum import IntEnum
|
||||
|
||||
import mgclient
|
||||
import pytest
|
||||
|
||||
|
||||
class Row(IntEnum):
|
||||
INDEX_TYPE = 0
|
||||
LABEL = 1
|
||||
PROPERTY = 2
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cursor(**kwargs) -> mgclient.Connection:
|
||||
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
|
||||
connection.autocommit = True
|
||||
cursor = connection.cursor()
|
||||
|
||||
cursor.execute("CREATE INDEX ON :Gene;")
|
||||
cursor.execute("CREATE INDEX ON :Gene(id);")
|
||||
cursor.execute("CREATE INDEX ON :Gene(i5);")
|
||||
cursor.execute("CREATE INDEX ON :Compound;")
|
||||
cursor.execute("CREATE INDEX ON :Compound(id);")
|
||||
cursor.execute("CREATE INDEX ON :Compound(mgid);")
|
||||
cursor.execute("CREATE INDEX ON :Compound(inchikey);")
|
||||
cursor.execute("CREATE INDEX ON :Anatomy;")
|
||||
cursor.execute("CREATE INDEX ON :Disease;")
|
||||
|
||||
yield cursor
|
||||
|
||||
cursor.execute("DROP INDEX ON :Gene;")
|
||||
cursor.execute("DROP INDEX ON :Gene(id);")
|
||||
cursor.execute("DROP INDEX ON :Gene(i5);")
|
||||
cursor.execute("DROP INDEX ON :Compound;")
|
||||
cursor.execute("DROP INDEX ON :Compound(id);")
|
||||
cursor.execute("DROP INDEX ON :Compound(mgid);")
|
||||
cursor.execute("DROP INDEX ON :Compound(inchikey);")
|
||||
cursor.execute("DROP INDEX ON :Anatomy;")
|
||||
cursor.execute("DROP INDEX ON :Disease;")
|
||||
|
||||
|
||||
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = dict()) -> typing.List[tuple]:
|
||||
cursor.execute(query, params)
|
||||
return cursor.fetchall()
|
||||
47
tests/e2e/show_index_info/test_show_index_info.py
Normal file
47
tests/e2e/show_index_info/test_show_index_info.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# Copyright 2023 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from common import Row, cursor, execute_and_fetch_all
|
||||
|
||||
|
||||
def test_show_index_info(cursor):
|
||||
index_info = execute_and_fetch_all(cursor, "SHOW INDEX INFO;")
|
||||
expected_index_info = {
|
||||
("label", "Anatomy", None),
|
||||
("label", "Compound", None),
|
||||
("label", "Disease", None),
|
||||
("label", "Gene", None),
|
||||
("label+property", "Compound", "id"),
|
||||
("label+property", "Compound", "inchikey"),
|
||||
("label+property", "Compound", "mgid"),
|
||||
("label+property", "Gene", "i5"),
|
||||
("label+property", "Gene", "id"),
|
||||
}
|
||||
assert set(index_info) == expected_index_info
|
||||
|
||||
|
||||
def test_index_info_sorted(cursor):
|
||||
index_info = execute_and_fetch_all(cursor, "SHOW INDEX INFO;")
|
||||
assert index_info == sorted(
|
||||
index_info,
|
||||
key=lambda index: (
|
||||
index[Row.INDEX_TYPE],
|
||||
index[Row.LABEL],
|
||||
index[Row.PROPERTY],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
13
tests/e2e/show_index_info/workloads.yaml
Normal file
13
tests/e2e/show_index_info/workloads.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
show_index_info: &show_index_info
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"]
|
||||
log_file: "test-show-index-info.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "test-show-index-info"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
args: ["show_index_info/test_show_index_info.py"]
|
||||
<<: *show_index_info
|
||||
167
tests/gql_behave/tests/memgraph_V1/features/map.feature
Normal file
167
tests/gql_behave/tests/memgraph_V1/features/map.feature
Normal file
@@ -0,0 +1,167 @@
|
||||
Feature: Creating map values
|
||||
|
||||
Scenario: Creating a map with multiple properties from a vertex
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Employee {name: "Andy", surname: "Walker", age: 24, id: 1234});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (e:Employee) RETURN {name: e.name, surname: e.surname, age: e.age} AS public_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| public_data |
|
||||
| {age: 24, name: 'Andy', surname: 'Walker'} |
|
||||
|
||||
Scenario: Creating instances of a map with multiple properties from a vertex
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Employee {name: "Andy", surname: "Walker", age: 24, id: 1234}), (:Person), (:Person);
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (e:Employee), (n:Person) RETURN {name: e.name, surname: e.surname, age: e.age} AS public_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| public_data |
|
||||
| {age: 24, name: 'Andy', surname: 'Walker'} |
|
||||
| {age: 24, name: 'Andy', surname: 'Walker'} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from each vertex
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Cat {name: "Luigi", age: 11}), (:Dog {name: "Don", age: 10}), (:Owner {name: "Ivan"});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m:Cat), (n:Dog), (o:Owner) SET o += {catName: m.name, catAge: m.age, dogName: n.name, dogAge: n.age} RETURN o;
|
||||
"""
|
||||
Then the result should be:
|
||||
| o |
|
||||
| (:Owner {catAge: 11, catName: 'Luigi', dogAge: 10, dogName: 'Don', name: 'Ivan'}) |
|
||||
|
||||
Scenario: Creating distinct maps with multiple properties, each from one vertex
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
FOREACH (i in range(1, 5) | CREATE (:Node {prop1: i, prop2: 2 * i}));
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n) RETURN {prop1: n.prop1, prop2: n.prop2} AS prop_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| prop_data |
|
||||
| {prop1: 1, prop2: 2} |
|
||||
| {prop1: 2, prop2: 4} |
|
||||
| {prop1: 3, prop2: 6} |
|
||||
| {prop1: 4, prop2: 8} |
|
||||
| {prop1: 5, prop2: 10} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from a vertex; one property is null
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (:Employee {name: "Andy", surname: "Walker", age: 24, id: 1234});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (e:Employee) RETURN {name: e.name, surname: e.surname, age: e.age, null_prop: e.nonexistent} AS public_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| public_data |
|
||||
| {age: 24, name: 'Andy', null_prop: null, surname: 'Walker'} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from an edge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (m)-[:ROUTE {km: 466, cross_border: true}]->(n);
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:ROUTE]->() RETURN {km: r.km, cross_border: r.cross_border} AS route_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| route_data |
|
||||
| {cross_border: true, km: 466} |
|
||||
|
||||
Scenario: Creating instances of a map with multiple properties from an edge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (m)-[:ROUTE {km: 466, cross_border: true}]->(n), (:City), (:City);
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (:City), ()-[r:ROUTE]->() RETURN {km: r.km, cross_border: r.cross_border} AS route_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| route_data |
|
||||
| {cross_border: true, km: 466} |
|
||||
| {cross_border: true, km: 466} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from each edge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (m)-[:HIGHWAY {km: 466, cross_border: true}]->(n), (m)-[:FLIGHT {km: 350, daily: true}]->(n);
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[h:HIGHWAY]->(), ()-[f:FLIGHT]->()
|
||||
RETURN {km_hwy: h.km, cross_border: h.cross_border, km_air: f.km, daily_flight: f.daily} AS routes_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| routes_data |
|
||||
| {cross_border: true, daily_flight: true, km_air: 350, km_hwy: 466} |
|
||||
|
||||
Scenario: Creating distinct maps with multiple properties, each from one edge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
MERGE (m:City) MERGE (n:Country) FOREACH (i in range(1, 5) | CREATE (m)-[:IN {prop1: i, prop2: 2 * i}]->(n));
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m)-[r]->(n) RETURN {prop1: r.prop1, prop2: r.prop2} AS prop_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| prop_data |
|
||||
| {prop1: 1, prop2: 2} |
|
||||
| {prop1: 2, prop2: 4} |
|
||||
| {prop1: 3, prop2: 6} |
|
||||
| {prop1: 4, prop2: 8} |
|
||||
| {prop1: 5, prop2: 10} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from an edge; one property is null
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (m)-[:ROUTE {km: 466, cross_border: true}]->(n);
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r:ROUTE]->() RETURN {km: r.km, cross_border: r.cross_border, null_prop: r.nonexistent} AS route_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| route_data |
|
||||
| {cross_border: true, km: 466, null_prop: null} |
|
||||
|
||||
Scenario: Creating a map with multiple properties from both a vertex and an edge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (m:City {name: "Split", highway_connected: true})-[:ROUTE {km: 466, cross_border: true}]->(n:City {name: "Ljubljana"});
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (m:City {name: "Split"})-[r:ROUTE]->()
|
||||
RETURN {km: r.km, cross_border: r.cross_border, start_city: m.name, highway_connected: m.highway_connected} AS route_data;
|
||||
"""
|
||||
Then the result should be:
|
||||
| route_data |
|
||||
| {cross_border: true, highway_connected: true, km: 466, start_city: 'Split'} |
|
||||
@@ -31,5 +31,8 @@ add_subdirectory(env_variable_check)
|
||||
#flag check binaries
|
||||
add_subdirectory(flag_check)
|
||||
|
||||
#flag check binaries
|
||||
#storage mode binaries
|
||||
add_subdirectory(storage_mode)
|
||||
|
||||
#run time settings binaries
|
||||
add_subdirectory(run_time_settings)
|
||||
|
||||
16
tests/integration/run_time_settings/CMakeLists.txt
Normal file
16
tests/integration/run_time_settings/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
set(target_name memgraph__integration__executor)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
set(flag_tester_target_name ${target_name}__flag_tester)
|
||||
set(executor_target_name ${target_name}__executor)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-communication)
|
||||
|
||||
add_executable(${flag_tester_target_name} flag_tester.cpp)
|
||||
set_target_properties(${flag_tester_target_name} PROPERTIES OUTPUT_NAME flag_tester)
|
||||
target_link_libraries(${flag_tester_target_name} mg-communication)
|
||||
|
||||
add_executable(${executor_target_name} executor.cpp)
|
||||
set_target_properties(${executor_target_name} PROPERTIES OUTPUT_NAME executor)
|
||||
target_link_libraries(${executor_target_name} mg-communication)
|
||||
53
tests/integration/run_time_settings/executor.cpp
Normal file
53
tests/integration/run_time_settings/executor.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "admin", "Username for the database");
|
||||
DEFINE_string(password, "admin", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
/**
|
||||
* Verifies that user 'user' has privileges that are given as positional
|
||||
* arguments.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
try {
|
||||
std::string query(argv[1]);
|
||||
auto ret = client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
LOG_FATAL(
|
||||
"The query shouldn't have failed but it failed with an "
|
||||
"error message '{}', {}",
|
||||
e.what(), argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
69
tests/integration/run_time_settings/flag_tester.cpp
Normal file
69
tests/integration/run_time_settings/flag_tester.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(field, "", "Expected settings field to check");
|
||||
DEFINE_string(value, "", "Expected string result from field");
|
||||
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(false);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
try {
|
||||
client.Connect(endpoint, "", "");
|
||||
} catch (const memgraph::utils::BasicException &e) {
|
||||
LOG_FATAL("");
|
||||
}
|
||||
|
||||
const auto &res = client.Execute("SHOW DATABASE SETTINGS", {});
|
||||
MG_ASSERT(res.fields[0] == "setting_name", "Expected \"setting_name\" field in the query result.");
|
||||
MG_ASSERT(res.fields[1] == "setting_value", "Expected \"setting_value\" field in the query result.");
|
||||
|
||||
unsigned i = 0;
|
||||
for (const auto &record : res.records) {
|
||||
const auto &settings_name = record[0].ValueString();
|
||||
if (settings_name == FLAGS_field) {
|
||||
const auto &settings_value = record[1].ValueString();
|
||||
// First try to encode the flags as float; if that fails just compare the raw strings
|
||||
try {
|
||||
MG_ASSERT(std::stof(settings_value) == std::stof(FLAGS_value),
|
||||
"Failed when checking \"{}\"; expected \"{}\", found \"{}\"!", FLAGS_field, FLAGS_value,
|
||||
settings_value);
|
||||
} catch (const std::invalid_argument &) {
|
||||
MG_ASSERT(settings_value == FLAGS_value, "Failed when checking \"{}\"; expected \"{}\", found \"{}\"!",
|
||||
FLAGS_field, FLAGS_value, settings_value);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_FATAL("No setting named \"{}\" found!", FLAGS_field);
|
||||
}
|
||||
202
tests/integration/run_time_settings/runner.py
Executable file
202
tests/integration/run_time_settings/runner.py
Executable file
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
# 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.
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import fcntl
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
|
||||
def wait_for_server(port: int, delay: float = 0.1) -> float:
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(
|
||||
binary,
|
||||
queries,
|
||||
should_fail=False,
|
||||
failure_message="",
|
||||
username="",
|
||||
password="",
|
||||
check_failure=True,
|
||||
connection_should_fail=False,
|
||||
):
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
if should_fail:
|
||||
args.append("--should-fail")
|
||||
if failure_message:
|
||||
args.extend(["--failure-message", failure_message])
|
||||
if check_failure:
|
||||
args.append("--check-failure")
|
||||
if connection_should_fail:
|
||||
args.append("--connection-should-fail")
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_query(binary: str, queries: List[str], username: str = "", password: str = "") -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def make_non_blocking(fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
|
||||
def start_memgraph(memgraph_args: List[any]) -> subprocess:
|
||||
memgraph = subprocess.Popen(
|
||||
list(map(str, memgraph_args)), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||
)
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
# Make the stdout and stderr pipes non-blocking
|
||||
make_non_blocking(memgraph.stdout.fileno())
|
||||
make_non_blocking(memgraph.stderr.fileno())
|
||||
return memgraph
|
||||
|
||||
|
||||
def check_flag(tester_binary: str, flag: str, value: str) -> None:
|
||||
args = [tester_binary, "--field", flag, "--value", value]
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def cleanup(memgraph: subprocess):
|
||||
if memgraph.poll() is None:
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
|
||||
def run_test(tester_binary: str, memgraph_args: List[str], server_name: str, query_tx: str):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
atexit.register(cleanup, memgraph)
|
||||
check_flag(tester_binary, "server.name", server_name)
|
||||
check_flag(tester_binary, "query.timeout", query_tx)
|
||||
cleanup(memgraph)
|
||||
atexit.unregister(cleanup)
|
||||
|
||||
|
||||
def run_test_w_query(tester_binary: str, memgraph_args: List[str], executor_binary: str):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
atexit.register(cleanup, memgraph)
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'server.name' TO 'New Name';"])
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'query.timeout' TO '123';"])
|
||||
check_flag(tester_binary, "server.name", "New Name")
|
||||
check_flag(tester_binary, "query.timeout", "123")
|
||||
cleanup(memgraph)
|
||||
atexit.unregister(cleanup)
|
||||
|
||||
|
||||
def consume(stream):
|
||||
res = []
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
break
|
||||
res.append(line.strip())
|
||||
return res
|
||||
|
||||
|
||||
def run_log_test(tester_binary: str, memgraph_args: List[str], executor_binary: str):
|
||||
# Test if command line parameters work
|
||||
memgraph = start_memgraph(memgraph_args + ["--log-level", "TRACE", "--also-log-to-stderr"])
|
||||
atexit.register(cleanup, memgraph)
|
||||
std_err = consume(memgraph.stderr)
|
||||
assert len(std_err) > 5, "Failed to log to stderr"
|
||||
# Test if run-time setting log.to_stderr works
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'log.to_stderr' TO 'false';"])
|
||||
consume(memgraph.stderr)
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'query.timeout' TO '123';"])
|
||||
std_err = consume(memgraph.stderr)
|
||||
assert len(std_err) == 0, "Still writing to stderr even after disabling it"
|
||||
# Test if run-time setting log.level works
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'log.to_stderr' TO 'true';"])
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'log.level' TO 'CRITICAL';"])
|
||||
consume(memgraph.stderr)
|
||||
execute_query(executor_binary, ["SET DATABASE SETTING 'query.timeout' TO '123';"])
|
||||
std_err = consume(memgraph.stderr)
|
||||
assert len(std_err) == 0, "Log level not updated"
|
||||
# Tets that unsupported values cause an exception
|
||||
execute_tester(
|
||||
tester_binary,
|
||||
["SET DATABASE SETTING 'log.to_stderr' TO 'something'"],
|
||||
should_fail=True,
|
||||
failure_message="'something' not valid for 'log.to_stderr'",
|
||||
)
|
||||
execute_tester(
|
||||
tester_binary,
|
||||
["SET DATABASE SETTING 'log.level' TO 'something'"],
|
||||
should_fail=True,
|
||||
failure_message="'something' not valid for 'log.level'",
|
||||
)
|
||||
cleanup(memgraph)
|
||||
atexit.unregister(cleanup)
|
||||
|
||||
|
||||
def execute_test(memgraph_binary: str, tester_binary: str, flag_tester_binary: str, executor_binary: str) -> None:
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
|
||||
|
||||
print("\033[1;36m~~ Starting run-time settings check test ~~\033[0m")
|
||||
|
||||
print("\033[1;34m~~ server.name and query.timeout ~~\033[0m")
|
||||
# Check default flags
|
||||
run_test(flag_tester_binary, memgraph_args, "Neo4j/v5.11.0 compatible graph database server - Memgraph", "600")
|
||||
|
||||
# Check changing flags via command-line arguments
|
||||
run_test(
|
||||
flag_tester_binary,
|
||||
memgraph_args + ["--bolt-server-name-for-init", "Memgraph", "--query-execution-timeout-sec", "1000"],
|
||||
"Memgraph",
|
||||
"1000",
|
||||
)
|
||||
|
||||
# Check changing flags via query
|
||||
run_test_w_query(flag_tester_binary, memgraph_args, executor_binary)
|
||||
|
||||
print("\033[1;34m~~ log.level and log.to_stderr ~~\033[0m")
|
||||
# Check log settings
|
||||
run_log_test(tester_binary, memgraph_args, executor_binary)
|
||||
|
||||
print("\033[1;36m~~ Finished run-time settings check test ~~\033[0m")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
|
||||
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "run_time_settings", "tester")
|
||||
flag_tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "run_time_settings", "flag_tester")
|
||||
executor_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "run_time_settings", "executor")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--tester", default=tester_binary)
|
||||
parser.add_argument("--flag_tester", default=flag_tester_binary)
|
||||
parser.add_argument("--executor", default=executor_binary)
|
||||
args = parser.parse_args()
|
||||
|
||||
execute_test(args.memgraph, args.tester, args.flag_tester, args.executor)
|
||||
|
||||
sys.exit(0)
|
||||
106
tests/integration/run_time_settings/tester.cpp
Normal file
106
tests/integration/run_time_settings/tester.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
|
||||
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
|
||||
DEFINE_bool(connection_should_fail, false, "Set to true to expect a connection failure.");
|
||||
DEFINE_string(failure_message, "", "Set to the expected failure message.");
|
||||
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
std::regex re(FLAGS_failure_message);
|
||||
|
||||
try {
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
} catch (const memgraph::communication::bolt::ClientFatalException &e) {
|
||||
if (FLAGS_connection_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && !std::regex_match(e.what(), re)) {
|
||||
LOG_FATAL(
|
||||
"The connection should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, e.what());
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The connection shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string query(argv[i]);
|
||||
try {
|
||||
client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
if (!FLAGS_check_failure) {
|
||||
if (!FLAGS_failure_message.empty() && std::regex_match(e.what(), re)) {
|
||||
LOG_FATAL(
|
||||
"The query should have succeeded or failed with an error "
|
||||
"message that isn't equal to '{}' but it failed with that error "
|
||||
"message",
|
||||
FLAGS_failure_message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (FLAGS_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && !std::regex_match(e.what(), re)) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, e.what());
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
if (!FLAGS_check_failure) continue;
|
||||
if (FLAGS_should_fail) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed but instead it executed "
|
||||
"successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import time
|
||||
from argparse import ArgumentParser
|
||||
from threading import Thread
|
||||
|
||||
from gqlalchemy import Memgraph
|
||||
from neo4j import TRUST_ALL_CERTIFICATES, GraphDatabase
|
||||
|
||||
|
||||
@@ -101,19 +100,6 @@ def execute_till_success(session, query, max_retries=1000):
|
||||
raise Exception("Query '%s' failed %d times, aborting" % (query, max_retries))
|
||||
|
||||
|
||||
def execute_till_success_gqlalchemy(memgraph: Memgraph, query: str, max_retries=1000):
|
||||
"""Same method as execute_till_success, but for gqlalchemy."""
|
||||
no_failures = 0
|
||||
while True:
|
||||
try:
|
||||
result = memgraph.execute(query)
|
||||
return result, no_failures
|
||||
except Exception:
|
||||
no_failures += 1
|
||||
if no_failures >= max_retries:
|
||||
raise Exception("Query '%s' failed %d times, aborting" % (query, max_retries))
|
||||
|
||||
|
||||
def batch(input, batch_size):
|
||||
"""Batches the given input (must be iterable).
|
||||
Supports input generators. Returns a generator.
|
||||
@@ -216,23 +202,6 @@ def argument_driver(args):
|
||||
)
|
||||
|
||||
|
||||
def get_memgraph(args) -> Memgraph:
|
||||
host_port = args.endpoint.split(":")
|
||||
|
||||
connection_params = {
|
||||
"host": host_port[0],
|
||||
"port": int(host_port[1]),
|
||||
"username": args.username,
|
||||
"password": args.password,
|
||||
"encrypted": False,
|
||||
}
|
||||
|
||||
if args.use_ssl:
|
||||
connection_params["encrypted"] = True
|
||||
|
||||
return Memgraph(**connection_params)
|
||||
|
||||
|
||||
# This class is used to create and cache sessions. Session is cached by args
|
||||
# used to create it and process' pid in which it was created. This makes it
|
||||
# easy to reuse session with python multiprocessing primitives like pmap.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
Large bipartite graph stress test.
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
import multiprocessing
|
||||
import random
|
||||
@@ -25,7 +26,12 @@ from dataclasses import dataclass
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Tuple
|
||||
|
||||
from common import OutputData, connection_argument_parser, get_memgraph
|
||||
from common import (
|
||||
OutputData,
|
||||
SessionCache,
|
||||
connection_argument_parser,
|
||||
execute_till_success,
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
output_data = OutputData()
|
||||
@@ -35,6 +41,9 @@ CREATE_FUNCTION = "CREATE"
|
||||
DELETE_FUNCTION = "DELETE"
|
||||
|
||||
|
||||
atexit.register(SessionCache.cleanup)
|
||||
|
||||
|
||||
def parse_args() -> Args:
|
||||
"""
|
||||
Parses user arguments
|
||||
@@ -98,20 +107,20 @@ def timed_function(name) -> Callable:
|
||||
|
||||
@timed_function("cleanup_time")
|
||||
def clean_database() -> None:
|
||||
memgraph = get_memgraph(args)
|
||||
memgraph.execute("MATCH (n) DETACH DELETE n")
|
||||
session = SessionCache.argument_session(args)
|
||||
execute_till_success(session, "MATCH (n) DETACH DELETE n")
|
||||
|
||||
|
||||
def create_indices() -> None:
|
||||
memgraph = get_memgraph(args)
|
||||
memgraph.execute("CREATE INDEX ON :Node")
|
||||
memgraph.execute("CREATE INDEX ON :Node(id)")
|
||||
session = SessionCache.argument_session(args)
|
||||
execute_till_success(session, "CREATE INDEX ON :Node")
|
||||
execute_till_success(session, "CREATE INDEX ON :Node(id)")
|
||||
|
||||
|
||||
def setup_database_mode() -> None:
|
||||
memgraph = get_memgraph(args)
|
||||
memgraph.execute(f"STORAGE MODE {args.storage_mode}")
|
||||
memgraph.execute(f"SET GLOBAL TRANSACTION ISOLATION LEVEL {args.isolation_level}")
|
||||
session = SessionCache.argument_session(args)
|
||||
execute_till_success(session, f"STORAGE MODE {args.storage_mode}")
|
||||
execute_till_success(session, f"SET GLOBAL TRANSACTION ISOLATION LEVEL {args.isolation_level}")
|
||||
|
||||
|
||||
def execute_function(worker: Worker) -> Worker:
|
||||
@@ -135,28 +144,28 @@ def run_writer(total_workers_cnt: int, repetition_count: int, sleep_sec: float,
|
||||
a valid graph. A graph is valid if the number of nodes is preserved, and the chain is either
|
||||
not present or present completely.
|
||||
"""
|
||||
memgraph = get_memgraph(args)
|
||||
session = SessionCache.argument_session(args)
|
||||
|
||||
def create():
|
||||
try:
|
||||
memgraph.execute(
|
||||
f"MERGE (:Node{worker_id} {{id: 1}})-[:REL]-(:Node{worker_id} {{id: 2}})-[:REL]-(:Node{worker_id} {{id: 3}})-[:REL]-(:Node{worker_id} {{id: 4}})"
|
||||
execute_till_success(
|
||||
session,
|
||||
f"MERGE (:Node{worker_id} {{id: 1}})-[:REL]-(:Node{worker_id} {{id: 2}})-[:REL]-(:Node{worker_id} {{id: 3}})-[:REL]-(:Node{worker_id} {{id: 4}})",
|
||||
)
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
def verify() -> Tuple[bool, int]:
|
||||
# We always create X nodes and therefore the number of nodes needs to be always a fraction of X
|
||||
count = list(memgraph.execute_and_fetch(f"MATCH (n) RETURN COUNT(n) AS cnt"))[0]["cnt"]
|
||||
count = execute_till_success(session, f"MATCH (n) RETURN COUNT(n) AS cnt")[0][0]["cnt"]
|
||||
log.info(f"Worker {worker_id} verified graph count {count} in repetition {curr_repetition}")
|
||||
|
||||
assert count <= total_workers_cnt * NUMBER_NODES_IN_CHAIN and count % NUMBER_NODES_IN_CHAIN == 0
|
||||
|
||||
ids = list(
|
||||
memgraph.execute_and_fetch(
|
||||
f"MATCH (n:Node{worker_id} {{id: 1}})-->(m)-->(o)-->(p) RETURN n.id AS id1, m.id AS id2, o.id AS id3, p.id AS id4"
|
||||
)
|
||||
)
|
||||
ids = execute_till_success(
|
||||
session,
|
||||
f"MATCH (n:Node{worker_id} {{id: 1}})-->(m)-->(o)-->(p) RETURN n.id AS id1, m.id AS id2, o.id AS id3, p.id AS id4",
|
||||
)[0]
|
||||
|
||||
if len(ids):
|
||||
result = ids[0]
|
||||
@@ -183,11 +192,11 @@ def run_deleter(total_workers_cnt: int, repetition_count: int, sleep_sec: float)
|
||||
"""
|
||||
Periodic deletion of an arbitrary chain in the graph
|
||||
"""
|
||||
memgraph = get_memgraph(args)
|
||||
session = SessionCache.argument_session(args)
|
||||
|
||||
def delete_part_of_graph(id: int):
|
||||
try:
|
||||
memgraph.execute(f"MATCH (n:Node{id}) DETACH DELETE n")
|
||||
execute_till_success(session, f"MATCH (n:Node{id}) DETACH DELETE n")
|
||||
log.info(f"Worker deleted chain with nodes of id {id}")
|
||||
except Exception as ex:
|
||||
log.info(f"Worker failed to delete the chain with id {id}")
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
neo4j-driver==4.1.1
|
||||
gqlalchemy==1.3.3
|
||||
|
||||
@@ -111,6 +111,9 @@ target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-qu
|
||||
add_unit_test(query_plan_match_filter_return.cpp)
|
||||
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-query mg-glue)
|
||||
|
||||
add_unit_test(query_plan_operator_to_string.cpp)
|
||||
target_link_libraries(${test_prefix}query_plan_operator_to_string mg-query)
|
||||
|
||||
add_unit_test(query_plan_read_write_typecheck.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/query/plan/read_write_type_checker.cpp)
|
||||
target_link_libraries(${test_prefix}query_plan_read_write_typecheck mg-query)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "glue/auth_global.hpp"
|
||||
#include "glue/auth_handler.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "utils/file.hpp"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "communication/result_stream_faker.hpp"
|
||||
#include "csv/parsing.hpp"
|
||||
#include "disk_test_utils.hpp"
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
@@ -56,8 +57,10 @@ class InterpreterTest : public ::testing::Test {
|
||||
|
||||
InterpreterTest()
|
||||
: data_directory(std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter"),
|
||||
interpreter_context(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)),
|
||||
{.execution_timeout_sec = 600}, data_directory) {}
|
||||
interpreter_context(std::make_unique<StorageType>(disk_test_utils::GenerateOnDiskConfig(testSuite)), {},
|
||||
data_directory) {
|
||||
memgraph::flags::run_time::execution_timeout_sec_ = 600.0;
|
||||
}
|
||||
|
||||
std::filesystem::path data_directory;
|
||||
memgraph::query::InterpreterContext interpreter_context;
|
||||
@@ -740,7 +743,7 @@ TYPED_TEST(InterpreterTest, ProfileQuery) {
|
||||
auto stream = this->Interpret("PROFILE MATCH (n) RETURN *;");
|
||||
std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
|
||||
EXPECT_EQ(stream.GetHeader(), expected_header);
|
||||
std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"};
|
||||
std::vector<std::string> expected_rows{"* Produce {n}", "* ScanAll (n)", "* Once"};
|
||||
ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
|
||||
auto expected_it = expected_rows.begin();
|
||||
for (const auto &row : stream.GetResults()) {
|
||||
@@ -764,7 +767,7 @@ TYPED_TEST(InterpreterTest, ProfileQueryMultiplePulls) {
|
||||
std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
|
||||
EXPECT_EQ(stream.GetHeader(), expected_header);
|
||||
|
||||
std::vector<std::string> expected_rows{"* Produce", "* ScanAll", "* Once"};
|
||||
std::vector<std::string> expected_rows{"* Produce {n}", "* ScanAll (n)", "* Once"};
|
||||
auto expected_it = expected_rows.begin();
|
||||
|
||||
this->Pull(&stream, 1);
|
||||
@@ -806,7 +809,7 @@ TYPED_TEST(InterpreterTest, ProfileQueryWithParams) {
|
||||
this->Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::PropertyValue(42)}});
|
||||
std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
|
||||
EXPECT_EQ(stream.GetHeader(), expected_header);
|
||||
std::vector<std::string> expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"};
|
||||
std::vector<std::string> expected_rows{"* Produce {n}", "* Filter", "* ScanAll (n)", "* Once"};
|
||||
ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
|
||||
auto expected_it = expected_rows.begin();
|
||||
for (const auto &row : stream.GetResults()) {
|
||||
|
||||
@@ -1653,6 +1653,21 @@ TYPED_TEST(QueryPlanTest, MergeNoInput) {
|
||||
EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
|
||||
}
|
||||
|
||||
TYPED_TEST(QueryPlanTest, SetPropertyWithCaching) {
|
||||
// SET (Null).prop = 42
|
||||
auto storage_dba = this->db->Access();
|
||||
memgraph::query::DbAccessor dba(storage_dba.get());
|
||||
SymbolTable symbol_table;
|
||||
auto prop = PROPERTY_PAIR(dba, "property");
|
||||
auto null = LITERAL(TypedValue());
|
||||
auto literal = LITERAL(42);
|
||||
auto n_prop = PROPERTY_LOOKUP(dba, null, prop);
|
||||
auto once = std::make_shared<Once>();
|
||||
auto set_op = std::make_shared<plan::SetProperty>(once, prop.second, n_prop, literal);
|
||||
auto context = MakeContext(this->storage, symbol_table, &dba);
|
||||
EXPECT_EQ(1, PullAll(*set_op, &context));
|
||||
}
|
||||
|
||||
TYPED_TEST(QueryPlanTest, SetPropertyOnNull) {
|
||||
// SET (Null).prop = 42
|
||||
auto storage_dba = this->db->Access();
|
||||
@@ -1717,6 +1732,46 @@ TYPED_TEST(QueryPlanTest, UpdateSetPropertiesFromMap) {
|
||||
EXPECT_EQ(*new_properties, expected_properties);
|
||||
}
|
||||
|
||||
TYPED_TEST(QueryPlanTest, SetPropertiesFromMapWithCaching) {
|
||||
auto storage_dba = this->db->Access();
|
||||
memgraph::query::DbAccessor dba(storage_dba.get());
|
||||
|
||||
// Add a single vertex. ({prop1: 43, prop2: 44})
|
||||
auto vertex_accessor = dba.InsertVertex();
|
||||
auto old_value = vertex_accessor.SetProperty(dba.NameToProperty("prop1"), memgraph::storage::PropertyValue{43});
|
||||
old_value = vertex_accessor.SetProperty(dba.NameToProperty("prop2"), memgraph::storage::PropertyValue{44});
|
||||
EXPECT_EQ(old_value.HasError(), false);
|
||||
EXPECT_EQ(*old_value, memgraph::storage::PropertyValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD)));
|
||||
|
||||
SymbolTable symbol_table;
|
||||
// MATCH (n) SET n += {new_prop1: n.prop1, new_prop2: n.prop2};
|
||||
auto n = MakeScanAll(this->storage, symbol_table, "n");
|
||||
auto prop_new_prop1 = PROPERTY_PAIR(dba, "new_prop1");
|
||||
auto prop_new_prop2 = PROPERTY_PAIR(dba, "new_prop2");
|
||||
std::unordered_map<PropertyIx, Expression *> prop_map;
|
||||
prop_map.emplace(this->storage.GetPropertyIx(prop_new_prop1.first), LITERAL(43));
|
||||
prop_map.emplace(this->storage.GetPropertyIx(prop_new_prop2.first), LITERAL(44));
|
||||
auto *rhs = this->storage.template Create<MapLiteral>(prop_map);
|
||||
|
||||
auto op_type{plan::SetProperties::Op::UPDATE};
|
||||
auto set_op = std::make_shared<plan::SetProperties>(n.op_, n.sym_, rhs, op_type);
|
||||
auto context = MakeContext(this->storage, symbol_table, &dba);
|
||||
PullAll(*set_op, &context);
|
||||
dba.AdvanceCommand();
|
||||
|
||||
auto new_properties = vertex_accessor.Properties(memgraph::storage::View::OLD);
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> expected_properties;
|
||||
expected_properties.emplace(dba.NameToProperty("prop1"), memgraph::storage::PropertyValue(43));
|
||||
expected_properties.emplace(dba.NameToProperty("prop2"), memgraph::storage::PropertyValue(44));
|
||||
expected_properties.emplace(dba.NameToProperty("new_prop1"), memgraph::storage::PropertyValue(43));
|
||||
expected_properties.emplace(dba.NameToProperty("new_prop2"), memgraph::storage::PropertyValue(44));
|
||||
EXPECT_EQ(context.evaluation_context.property_lookups_cache.size(), 0);
|
||||
EXPECT_EQ(new_properties.HasError(), false);
|
||||
EXPECT_EQ(*new_properties, expected_properties);
|
||||
}
|
||||
|
||||
TYPED_TEST(QueryPlanTest, SetLabelsOnNull) {
|
||||
// OPTIONAL MATCH (n) SET n :label
|
||||
auto storage_dba = this->db->Access();
|
||||
|
||||
497
tests/unit/query_plan_operator_to_string.cpp
Normal file
497
tests/unit/query_plan_operator_to_string.cpp
Normal file
@@ -0,0 +1,497 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "disk_test_utils.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/pretty_print.hpp"
|
||||
|
||||
#include "query_common.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/inmemory/storage.hpp"
|
||||
|
||||
using namespace memgraph::query;
|
||||
using namespace memgraph::query::plan;
|
||||
|
||||
// The JSON formatted plan is consumed (or will be) by Memgraph Lab, and
|
||||
// therefore should not be changed before synchronizing with whoever is
|
||||
// maintaining Memgraph Lab. Hopefully, one day integration tests will exist and
|
||||
// there will be no need to be super careful.
|
||||
|
||||
template <typename StorageType>
|
||||
class OperatorToStringTest : public ::testing::Test {
|
||||
protected:
|
||||
const std::string testSuite = "plan_operator_to_string";
|
||||
|
||||
OperatorToStringTest()
|
||||
: config(disk_test_utils::GenerateOnDiskConfig(testSuite)),
|
||||
db(new StorageType(config)),
|
||||
dba_storage(db->Access()),
|
||||
dba(dba_storage.get()) {}
|
||||
|
||||
~OperatorToStringTest() {
|
||||
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
|
||||
disk_test_utils::RemoveRocksDbDirs(testSuite);
|
||||
}
|
||||
}
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
memgraph::storage::Config config;
|
||||
std::unique_ptr<memgraph::storage::Storage> db;
|
||||
std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage;
|
||||
memgraph::query::DbAccessor dba;
|
||||
|
||||
Symbol GetSymbol(std::string name) { return symbol_table.CreateSymbol(name, true); }
|
||||
};
|
||||
|
||||
using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgraph::storage::DiskStorage>;
|
||||
TYPED_TEST_CASE(OperatorToStringTest, StorageTypes);
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Once) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<Once>();
|
||||
|
||||
std::string expected_string{"Once"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, CreateNode) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<CreateNode>(
|
||||
nullptr, NodeCreationInfo{this->GetSymbol("node"),
|
||||
{this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")},
|
||||
{{this->dba.NameToProperty("prop1"), LITERAL(5)},
|
||||
{this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}});
|
||||
|
||||
std::string expected_string{"CreateNode"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, CreateExpand) {
|
||||
Symbol node1_sym = this->GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node1"));
|
||||
last_op = std::make_shared<CreateExpand>(
|
||||
NodeCreationInfo{this->GetSymbol("node2"),
|
||||
{this->dba.NameToLabel("Label1"), this->dba.NameToLabel("Label2")},
|
||||
{{this->dba.NameToProperty("prop1"), LITERAL(5)},
|
||||
{this->dba.NameToProperty("prop2"), LITERAL("some cool stuff")}}},
|
||||
EdgeCreationInfo{this->GetSymbol("edge"),
|
||||
{{this->dba.NameToProperty("weight"), LITERAL(5.32)}},
|
||||
this->dba.NameToEdgeType("edge_type"),
|
||||
EdgeAtom::Direction::OUT},
|
||||
last_op, node1_sym, false);
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"CreateExpand (node1)-[edge:edge_type]->(node2)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAll) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node"));
|
||||
|
||||
std::string expected_string{"ScanAll (node)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAllByLabel) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabel>(nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"));
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"ScanAllByLabel (node :Label)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAllByLabelPropertyRange) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyRange>(
|
||||
nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop",
|
||||
memgraph::utils::MakeBoundInclusive<Expression *>(LITERAL(1)),
|
||||
memgraph::utils::MakeBoundExclusive<Expression *>(LITERAL(20)));
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"ScanAllByLabelPropertyRange (node :Label {prop})"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAllByLabelPropertyValue) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelPropertyValue>(
|
||||
nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"), this->dba.NameToProperty("prop"), "prop",
|
||||
ADD(LITERAL(21), LITERAL(21)));
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"ScanAllByLabelPropertyValue (node :Label {prop})"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAllByLabelProperty) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllByLabelProperty>(nullptr, this->GetSymbol("node"), this->dba.NameToLabel("Label"),
|
||||
this->dba.NameToProperty("prop"), "prop");
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"ScanAllByLabelProperty (node :Label {prop})"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ScanAllById) {
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<ScanAllById>(nullptr, this->GetSymbol("node"), ADD(LITERAL(21), LITERAL(21)));
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"ScanAllById (node)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Expand) {
|
||||
auto node1_sym = this->GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"),
|
||||
EdgeAtom::Direction::BOTH,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"),
|
||||
this->dba.NameToEdgeType("EdgeType2")},
|
||||
false, memgraph::storage::View::OLD);
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"Expand (node1)-[edge:EdgeType1|:EdgeType2]-(node2)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ExpandVariable) {
|
||||
auto node1_sym = this->GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<ExpandVariable>(
|
||||
last_op, node1_sym, this->GetSymbol("node2"), this->GetSymbol("edge"), EdgeAtom::Type::BREADTH_FIRST,
|
||||
EdgeAtom::Direction::OUT,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{this->dba.NameToEdgeType("EdgeType1"),
|
||||
this->dba.NameToEdgeType("EdgeType2")},
|
||||
false, LITERAL(2), LITERAL(5), false,
|
||||
ExpansionLambda{this->GetSymbol("inner_node"), this->GetSymbol("inner_edge"),
|
||||
PROPERTY_LOOKUP(this->dba, "inner_node", this->dba.NameToProperty("unblocked"))},
|
||||
std::nullopt, std::nullopt);
|
||||
last_op->dba_ = &this->dba;
|
||||
|
||||
std::string expected_string{"BFSExpand (node1)-[edge:EdgeType1|:EdgeType2]->(node2)"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, ConstructNamedPath) {
|
||||
auto node1_sym = this->GetSymbol("node1");
|
||||
auto edge1_sym = this->GetSymbol("edge1");
|
||||
auto node2_sym = this->GetSymbol("node2");
|
||||
auto edge2_sym = this->GetSymbol("edge2");
|
||||
auto node3_sym = this->GetSymbol("node3");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD);
|
||||
last_op = std::make_shared<Expand>(last_op, node2_sym, node3_sym, edge2_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD);
|
||||
last_op = std::make_shared<ConstructNamedPath>(
|
||||
last_op, this->GetSymbol("path"), std::vector<Symbol>{node1_sym, edge1_sym, node2_sym, edge2_sym, node3_sym});
|
||||
|
||||
std::string expected_string{"ConstructNamedPath"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Filter) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node1"));
|
||||
last_op =
|
||||
std::make_shared<Filter>(last_op, std::vector<std::shared_ptr<LogicalOperator>>{},
|
||||
EQ(PROPERTY_LOOKUP(this->dba, "node1", this->dba.NameToProperty("prop")), LITERAL(5)));
|
||||
|
||||
std::string expected_string{"Filter"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Produce) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Produce>(
|
||||
nullptr, std::vector<NamedExpression *>{NEXPR("pet", LITERAL(5)), NEXPR("string", LITERAL("string"))});
|
||||
|
||||
std::string expected_string{"Produce {pet, string}"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Delete) {
|
||||
auto node_sym = this->GetSymbol("node1");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<Expand>(last_op, node_sym, this->GetSymbol("node2"), this->GetSymbol("edge"),
|
||||
EdgeAtom::Direction::BOTH, std::vector<memgraph::storage::EdgeTypeId>{}, false,
|
||||
memgraph::storage::View::OLD);
|
||||
last_op = std::make_shared<plan::Delete>(last_op, std::vector<Expression *>{IDENT("node2")}, true);
|
||||
|
||||
std::string expected_string{"Delete"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, SetProperty) {
|
||||
memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node"));
|
||||
last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop),
|
||||
ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1)));
|
||||
|
||||
std::string expected_string{"SetProperty"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, SetProperties) {
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetProperties>(last_op, node_sym,
|
||||
MAP({{this->storage.GetPropertyIx("prop1"), LITERAL(1)},
|
||||
{this->storage.GetPropertyIx("prop2"), LITERAL("propko")}}),
|
||||
plan::SetProperties::Op::REPLACE);
|
||||
|
||||
std::string expected_string{"SetProperties"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, SetLabels) {
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetLabels>(
|
||||
last_op, node_sym,
|
||||
std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")});
|
||||
|
||||
std::string expected_string{"SetLabels"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, RemoveProperty) {
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::RemoveProperty>(
|
||||
last_op, this->dba.NameToProperty("prop"), PROPERTY_LOOKUP(this->dba, "node", this->dba.NameToProperty("prop")));
|
||||
|
||||
std::string expected_string{"RemoveProperty"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, RemoveLabels) {
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::RemoveLabels>(
|
||||
last_op, node_sym,
|
||||
std::vector<memgraph::storage::LabelId>{this->dba.NameToLabel("label1"), this->dba.NameToLabel("label2")});
|
||||
|
||||
std::string expected_string{"RemoveLabels"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, EdgeUniquenessFilter) {
|
||||
auto node1_sym = this->GetSymbol("node1");
|
||||
auto node2_sym = this->GetSymbol("node2");
|
||||
auto node3_sym = this->GetSymbol("node3");
|
||||
auto node4_sym = this->GetSymbol("node4");
|
||||
|
||||
auto edge1_sym = this->GetSymbol("edge1");
|
||||
auto edge2_sym = this->GetSymbol("edge2");
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
last_op = std::make_shared<Expand>(last_op, node1_sym, node2_sym, edge1_sym, EdgeAtom::Direction::IN,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD);
|
||||
last_op = std::make_shared<ScanAll>(last_op, node3_sym);
|
||||
last_op = std::make_shared<Expand>(last_op, node3_sym, node4_sym, edge2_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD);
|
||||
last_op = std::make_shared<EdgeUniquenessFilter>(last_op, edge2_sym, std::vector<Symbol>{edge1_sym});
|
||||
|
||||
std::string expected_string{"EdgeUniquenessFilter"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Accumulate) {
|
||||
memgraph::storage::PropertyId prop = this->dba.NameToProperty("prop");
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::SetProperty>(last_op, prop, PROPERTY_LOOKUP(this->dba, "node", prop),
|
||||
ADD(PROPERTY_LOOKUP(this->dba, "node", prop), LITERAL(1)));
|
||||
last_op = std::make_shared<plan::Accumulate>(last_op, std::vector<Symbol>{node_sym}, true);
|
||||
|
||||
std::string expected_string{"Accumulate"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Aggregate) {
|
||||
memgraph::storage::PropertyId value = this->dba.NameToProperty("value");
|
||||
memgraph::storage::PropertyId color = this->dba.NameToProperty("color");
|
||||
memgraph::storage::PropertyId type = this->dba.NameToProperty("type");
|
||||
auto node_sym = this->GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<plan::Aggregate>(
|
||||
nullptr,
|
||||
std::vector<Aggregate::Element>{
|
||||
{PROPERTY_LOOKUP(this->dba, "node", value), nullptr, Aggregation::Op::SUM, this->GetSymbol("sum")},
|
||||
{PROPERTY_LOOKUP(this->dba, "node", value), PROPERTY_LOOKUP(this->dba, "node", color),
|
||||
Aggregation::Op::COLLECT_MAP, this->GetSymbol("map")},
|
||||
{nullptr, nullptr, Aggregation::Op::COUNT, this->GetSymbol("count")}},
|
||||
std::vector<Expression *>{PROPERTY_LOOKUP(this->dba, "node", type)}, std::vector<Symbol>{node_sym});
|
||||
|
||||
std::string expected_string{"Aggregate {sum, map, count} {node}"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Skip) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node"));
|
||||
last_op = std::make_shared<Skip>(last_op, LITERAL(42));
|
||||
|
||||
std::string expected_string{"Skip"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Limit) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, this->GetSymbol("node"));
|
||||
last_op = std::make_shared<Limit>(last_op, LITERAL(42));
|
||||
|
||||
std::string expected_string{"Limit"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, OrderBy) {
|
||||
Symbol person_sym = this->GetSymbol("person");
|
||||
Symbol pet_sym = this->GetSymbol("pet");
|
||||
memgraph::storage::PropertyId name = this->dba.NameToProperty("name");
|
||||
memgraph::storage::PropertyId age = this->dba.NameToProperty("age");
|
||||
std::shared_ptr<LogicalOperator> last_op;
|
||||
last_op = std::make_shared<OrderBy>(nullptr,
|
||||
std::vector<SortItem>{{Ordering::ASC, PROPERTY_LOOKUP(this->dba, "person", name)},
|
||||
{Ordering::DESC, PROPERTY_LOOKUP(this->dba, "pet", age)}},
|
||||
std::vector<Symbol>{person_sym, pet_sym});
|
||||
|
||||
std::string expected_string{"OrderBy {person, pet}"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Merge) {
|
||||
Symbol node_sym = this->GetSymbol("node");
|
||||
memgraph::storage::LabelId label = this->dba.NameToLabel("label");
|
||||
|
||||
std::shared_ptr<LogicalOperator> match = std::make_shared<ScanAllByLabel>(nullptr, node_sym, label);
|
||||
|
||||
std::shared_ptr<LogicalOperator> create =
|
||||
std::make_shared<CreateNode>(nullptr, NodeCreationInfo{node_sym, {label}, {}});
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<plan::Merge>(nullptr, match, create);
|
||||
|
||||
std::string expected_string{"Merge"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Optional) {
|
||||
Symbol node1_sym = this->GetSymbol("node1");
|
||||
Symbol node2_sym = this->GetSymbol("node2");
|
||||
Symbol edge_sym = this->GetSymbol("edge");
|
||||
|
||||
std::shared_ptr<LogicalOperator> input = std::make_shared<ScanAll>(nullptr, node1_sym);
|
||||
|
||||
std::shared_ptr<LogicalOperator> expand =
|
||||
std::make_shared<Expand>(nullptr, node1_sym, node2_sym, edge_sym, EdgeAtom::Direction::OUT,
|
||||
std::vector<memgraph::storage::EdgeTypeId>{}, false, memgraph::storage::View::OLD);
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<Optional>(input, expand, std::vector<Symbol>{node2_sym, edge_sym});
|
||||
|
||||
std::string expected_string{"Optional"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Unwind) {
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(1), LITERAL(2), LITERAL(3)), this->GetSymbol("x"));
|
||||
|
||||
std::string expected_string{"Unwind"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Distinct) {
|
||||
Symbol x = this->GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> last_op =
|
||||
std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x);
|
||||
last_op = std::make_shared<Distinct>(last_op, std::vector<Symbol>{x});
|
||||
|
||||
std::string expected_string{"Distinct"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Union) {
|
||||
Symbol x = this->GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> lhs =
|
||||
std::make_shared<plan::Unwind>(nullptr, LIST(LITERAL(2), LITERAL(3), LITERAL(2)), x);
|
||||
|
||||
Symbol node = this->GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> rhs = std::make_shared<ScanAll>(nullptr, node);
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<Union>(
|
||||
lhs, rhs, std::vector<Symbol>{this->GetSymbol("x")}, std::vector<Symbol>{x}, std::vector<Symbol>{node});
|
||||
|
||||
std::string expected_string{"Union {x : x}"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, CallProcedure) {
|
||||
memgraph::query::plan::CallProcedure call_op;
|
||||
call_op.input_ = std::make_shared<Once>();
|
||||
call_op.procedure_name_ = "mg.procedures";
|
||||
call_op.arguments_ = {};
|
||||
call_op.result_fields_ = {"is_editable", "is_write", "name", "path", "signature"};
|
||||
call_op.result_symbols_ = {this->GetSymbol("is_editable"), this->GetSymbol("is_write"), this->GetSymbol("name"),
|
||||
this->GetSymbol("path"), this->GetSymbol("signature")};
|
||||
|
||||
std::string expected_string{"CallProcedure<mg.procedures> {is_editable, is_write, name, path, signature}"};
|
||||
EXPECT_EQ(call_op.ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, LoadCsv) {
|
||||
memgraph::query::plan::LoadCsv last_op;
|
||||
last_op.input_ = std::make_shared<Once>();
|
||||
last_op.row_var_ = this->GetSymbol("transaction");
|
||||
|
||||
std::string expected_string{"LoadCsv {transaction}"};
|
||||
EXPECT_EQ(last_op.ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Foreach) {
|
||||
Symbol x = this->GetSymbol("x");
|
||||
std::shared_ptr<LogicalOperator> create = std::make_shared<CreateNode>(
|
||||
nullptr, NodeCreationInfo{this->GetSymbol("node"), {this->dba.NameToLabel("Label1")}, {}});
|
||||
std::shared_ptr<LogicalOperator> foreach =
|
||||
std::make_shared<plan::Foreach>(nullptr, std::move(create), LIST(LITERAL(1)), x);
|
||||
|
||||
std::string expected_string{"Foreach"};
|
||||
EXPECT_EQ(foreach->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, EmptyResult) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<EmptyResult>(nullptr);
|
||||
|
||||
std::string expected_string{"EmptyResult"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, EvaluatePatternFilter) {
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<EvaluatePatternFilter>(nullptr, this->GetSymbol("node"));
|
||||
|
||||
std::string expected_string{"EvaluatePatternFilter"};
|
||||
EXPECT_EQ(last_op->ToString(), expected_string);
|
||||
}
|
||||
|
||||
TYPED_TEST(OperatorToStringTest, Apply) {
|
||||
memgraph::query::plan::Apply last_op(nullptr, nullptr, false);
|
||||
|
||||
std::string expected_string{"Apply"};
|
||||
EXPECT_EQ(last_op.ToString(), expected_string);
|
||||
}
|
||||
@@ -1020,7 +1020,7 @@ TYPED_TEST(TestSymbolGenerator, MatchUnionReturnSymbols) {
|
||||
EXPECT_EQ(symbol_table.max_position(), 8);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticExpcetion) {
|
||||
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticException) {
|
||||
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 3 AS Z, 4 AS Y
|
||||
auto ret = this->storage.template Create<Return>();
|
||||
ret->body_.all_identifiers = true;
|
||||
@@ -1029,7 +1029,7 @@ TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNameThrowSemanticExpcetion) {
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticExpcetion) {
|
||||
TYPED_TEST(TestSymbolGenerator, MatchUnionParameterNumberThrowSemanticException) {
|
||||
// WITH 1 as X, 2 AS Y RETURN * UNION RETURN 4 AS Y
|
||||
auto ret = this->storage.template Create<Return>();
|
||||
ret->body_.all_identifiers = true;
|
||||
@@ -1278,3 +1278,166 @@ TYPED_TEST(TestSymbolGenerator, Subqueries) {
|
||||
symbol_table = MakeSymbolTable(query);
|
||||
ASSERT_EQ(symbol_table.max_position(), 13);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingSingleLookup) {
|
||||
// WITH {icode: 0000} AS item
|
||||
// RETURN {icode: item.icode} AS new_map;
|
||||
|
||||
auto prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto prop1_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("icode"));
|
||||
|
||||
auto has_properties = MAP({prop1_key, LITERAL(0000)});
|
||||
auto new_map = MAP({prop1_key, prop1_val});
|
||||
auto query = QUERY(SINGLE_QUERY(WITH(has_properties, AS("item")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop1_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingTwoSingleLookups) {
|
||||
// WITH {icode: 0000} AS item1, {icode: 1111} AS item2
|
||||
// RETURN {icode1: item1.icode, icode2: item2.icode} AS new_map;
|
||||
|
||||
auto in_prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
|
||||
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
|
||||
auto out_prop2_key = this->storage.GetPropertyIx("icode2");
|
||||
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
|
||||
|
||||
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)});
|
||||
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)});
|
||||
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val});
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
|
||||
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
|
||||
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingMultipleLookup) {
|
||||
// WITH {icode: 0000, price: 10} AS item
|
||||
// RETURN {icode: item.icode, price: item.price} AS new_map;
|
||||
|
||||
auto prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto prop1_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("icode"));
|
||||
auto prop2_key = this->storage.GetPropertyIx("price");
|
||||
auto prop2_val = PROPERTY_LOOKUP(this->dba, "item", this->dba.NameToProperty("price"));
|
||||
|
||||
auto has_properties = MAP({prop1_key, LITERAL(0000)}, {prop2_key, LITERAL(10)});
|
||||
auto new_map = MAP({prop1_key, prop1_val}, {prop2_key, prop2_val});
|
||||
auto query = QUERY(SINGLE_QUERY(WITH(has_properties, AS("item")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop1_key])->evaluation_mode_;
|
||||
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[prop2_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingTwoMultipleLookups) {
|
||||
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
|
||||
// RETURN {icode1: item1.icode, price1: item1.price, icode2: item2.icode, price2: item2.price} AS new_map;
|
||||
|
||||
auto in_prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto in_prop2_key = this->storage.GetPropertyIx("price");
|
||||
|
||||
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
|
||||
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
|
||||
auto out_prop2_key = this->storage.GetPropertyIx("price1");
|
||||
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("price"));
|
||||
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
|
||||
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
|
||||
auto out_prop4_key = this->storage.GetPropertyIx("price2");
|
||||
auto out_prop4_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("price"));
|
||||
|
||||
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
|
||||
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
|
||||
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val}, {out_prop3_key, out_prop3_val},
|
||||
{out_prop4_key, out_prop4_val});
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
|
||||
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
|
||||
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
|
||||
auto prop4_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop4_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop4_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingMixedLookups1) {
|
||||
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
|
||||
// RETURN {icode1: item1.icode, price1: item1.price, icode2: item2.icode} AS new_map;
|
||||
|
||||
auto in_prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto in_prop2_key = this->storage.GetPropertyIx("price");
|
||||
|
||||
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
|
||||
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
|
||||
auto out_prop2_key = this->storage.GetPropertyIx("price1");
|
||||
auto out_prop2_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("price"));
|
||||
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
|
||||
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
|
||||
|
||||
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
|
||||
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
|
||||
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop2_key, out_prop2_val}, {out_prop3_key, out_prop3_val});
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
|
||||
auto prop2_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop2_key])->evaluation_mode_;
|
||||
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop2_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
|
||||
}
|
||||
|
||||
TYPED_TEST(TestSymbolGenerator, PropertyCachingMixedLookups2) {
|
||||
// WITH {icode: 0000, price: 10} AS item1, {icode: 1111, price: 16} AS item2
|
||||
// RETURN {icode1: item1.icode, icode2: item2.icode, price2: item2.price} AS new_map;
|
||||
|
||||
auto in_prop1_key = this->storage.GetPropertyIx("icode");
|
||||
auto in_prop2_key = this->storage.GetPropertyIx("price");
|
||||
|
||||
auto out_prop1_key = this->storage.GetPropertyIx("icode1");
|
||||
auto out_prop1_val = PROPERTY_LOOKUP(this->dba, "item1", this->dba.NameToProperty("icode"));
|
||||
auto out_prop3_key = this->storage.GetPropertyIx("icode2");
|
||||
auto out_prop3_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("icode"));
|
||||
auto out_prop4_key = this->storage.GetPropertyIx("price2");
|
||||
auto out_prop4_val = PROPERTY_LOOKUP(this->dba, "item2", this->dba.NameToProperty("price"));
|
||||
|
||||
auto has_properties1 = MAP({in_prop1_key, LITERAL(0000)}, {in_prop2_key, LITERAL(10)});
|
||||
auto has_properties2 = MAP({in_prop1_key, LITERAL(1111)}, {in_prop2_key, LITERAL(16)});
|
||||
auto new_map = MAP({out_prop1_key, out_prop1_val}, {out_prop3_key, out_prop3_val}, {out_prop4_key, out_prop4_val});
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(WITH(has_properties1, AS("item1"), has_properties2, AS("item2")), RETURN(new_map, AS("new_map"))));
|
||||
|
||||
memgraph::query::MakeSymbolTable(query);
|
||||
|
||||
auto prop1_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop1_key])->evaluation_mode_;
|
||||
auto prop3_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop3_key])->evaluation_mode_;
|
||||
auto prop4_eval_mode = dynamic_cast<PropertyLookup *>(new_map->elements_[out_prop4_key])->evaluation_mode_;
|
||||
|
||||
ASSERT_TRUE(prop1_eval_mode == PropertyLookup::EvaluationMode::GET_OWN_PROPERTY);
|
||||
ASSERT_TRUE(prop3_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
ASSERT_TRUE(prop4_eval_mode == PropertyLookup::EvaluationMode::GET_ALL_PROPERTIES);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user