Compare commits

...

24 Commits

Author SHA1 Message Date
Josip Mrden
e71b9a99ae Add line breaks 2023-09-15 13:17:49 +02:00
Josip Mrden
2aa6b9ba8f Add null value to transformations 2023-09-15 12:44:19 +02:00
Josip Mrden
6c9cdef944 Make example transformation work e2e 2023-09-15 12:39:06 +02:00
Josip Mrden
5c10fb1d51 Add building of query module default libraries without the lib prefix 2023-09-15 12:09:27 +02:00
Josip Mrden
e73b3feea3 Adjust equals operator on messages iterator 2023-09-15 10:40:16 +02:00
Josip Mrden
2ed40e9f16 Adjust name of the transformation 2023-09-15 10:24:23 +02:00
Josip Mrden
ced34bc79c Add example transformation module 2023-09-15 10:20:22 +02:00
Josip Mrden
2b3540119f Implement equals 2023-09-14 19:17:20 +02:00
Josip Mrden
3936668e13 Implemented messages interface 2023-09-14 18:07:27 +02:00
Josip Mrden
476080bace Add iterator constructors 2023-09-14 18:02:08 +02:00
Josip Mrden
8528822af1 Add blueprint for message iterator 2023-09-14 17:57:15 +02:00
Josip Mrden
7ecf970dcd Add messages methods 2023-09-14 17:47:09 +02:00
Josip Mrden
6ad5463d72 Add blueprint for classes 2023-09-14 17:01:37 +02:00
Josip Mrden
b1d560737e Add error wrappers 2023-09-14 16:45:27 +02:00
Marko Barišić
b719f0744f Update v2.11 license date (#1247) 2023-09-13 07:19:11 -04:00
Josipmrden
440838c0e9 Add dependency check for e2e tests (#1240) 2023-09-12 11:34:08 -04:00
Josipmrden
79a3c5af8e Add manual performance benchmark execution (#1239) 2023-09-12 11:33:05 -04:00
Josipmrden
bf03b38e39 Remove gqlalchemy from stress tests (#1245) 2023-09-12 11:32:16 -04:00
gvolfing
fd63944493 Add --query-callable-mappings-path package default (#1203) 2023-09-11 15:12:14 -04:00
Gareth Andrew Lloyd
6694de2dfa Fix libkrb5 TRUE and FALSE macros leakage (#1243)
Co-authored-by: Marko Budiselić <marko.budiselic@memgraph.com>
2023-09-11 12:46:40 -04:00
andrejtonev
5e5f4ffc5d Add more runtime configurable settings (#1183)
server name, query timeout settings, log.level, log.to_stderr
2023-09-11 11:30:54 -04:00
Ante Pušić
060b9d1c16 [master < T1204] Add detailed operator info to PROFILE and EXPLAIN (#1204) 2023-09-11 14:34:27 +02:00
Ante Pušić
29a505cb38 Property lookup caching (#1168) 2023-09-11 13:03:54 +02:00
Ante Pušić
d4fcd745d2 Sort SHOW INDEX INFO (#1178) 2023-09-11 10:59:41 +02:00
85 changed files with 2859 additions and 325 deletions

View File

@@ -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.

View 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 }}"

View File

@@ -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
View 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"
}

View File

@@ -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

View File

@@ -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> &parameters,
@@ -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

View File

@@ -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.

View File

@@ -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)

View 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; }

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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 {
/**

View File

@@ -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"

View File

@@ -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
View 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)

View File

@@ -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"

View File

@@ -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.");

View File

@@ -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);

View File

@@ -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.");

View File

@@ -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);
}

View File

@@ -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

View 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

View 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

View File

@@ -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
View 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

View File

@@ -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_;

View File

@@ -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,

View File

@@ -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()

View File

@@ -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};

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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 Dont 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]);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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_));

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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})

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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 ///

View File

@@ -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"

View File

@@ -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.

View File

@@ -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);

View File

@@ -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_;
};

View File

@@ -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

View 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!")

View File

@@ -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!")

View File

@@ -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
View 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

View File

@@ -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)

View File

@@ -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
View 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"

View 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)

View 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()

View 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"]))

View 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

View 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'} |

View File

@@ -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)

View 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)

View 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;
}

View 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);
}

View 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)

View 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;
}

View File

@@ -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.

View File

@@ -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}")

View File

@@ -1,2 +1 @@
neo4j-driver==4.1.1
gqlalchemy==1.3.3

View File

@@ -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)

View File

@@ -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"

View File

@@ -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()) {

View File

@@ -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();

View 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);
}

View File

@@ -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);
}