Merge branch 'master' into add-bug-tracking-workflow

This commit is contained in:
Jure Bajic 2023-01-26 07:42:08 +01:00 committed by GitHub
commit 1601a03e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 636 additions and 259 deletions

View File

@ -14,6 +14,7 @@ on:
- "**/*.md" - "**/*.md"
- ".clang-format" - ".clang-format"
- "CODEOWNERS" - "CODEOWNERS"
- "licenses/*"
jobs: jobs:
community_build: community_build:

View File

@ -6,7 +6,7 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.10.0 rev: 22.8.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -378,6 +378,10 @@ inline mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name,
return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory); return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory);
} }
inline void vertex_set_property(mgp_vertex *v, const char *property_name, mgp_value *property_value) {
MgInvokeVoid(mgp_vertex_set_property, v, property_name, property_value);
}
inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) { inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory); return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
} }
@ -410,6 +414,10 @@ inline mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_
return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory); return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory);
} }
inline void edge_set_property(mgp_edge *e, const char *property_name, mgp_value *property_value) {
MgInvokeVoid(mgp_edge_set_property, e, property_name, property_value);
}
inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) { inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory); return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
} }

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -66,9 +66,12 @@ struct MapItem;
class Duration; class Duration;
class Value; class Value;
struct StealType {};
inline constexpr StealType steal{};
inline mgp_memory *memory{nullptr}; inline mgp_memory *memory{nullptr};
/* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships, Properties & Labels) */ /* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships & Labels) */
/// Wrapper for int64_t IDs to prevent dangerous implicit conversions. /// Wrapper for int64_t IDs to prevent dangerous implicit conversions.
class Id { class Id {
@ -159,7 +162,7 @@ class Nodes {
explicit Iterator(mgp_vertices_iterator *nodes_iterator); explicit Iterator(mgp_vertices_iterator *nodes_iterator);
Iterator(const Iterator &other); Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete; Iterator &operator=(const Iterator &other) = delete;
~Iterator(); ~Iterator();
@ -207,7 +210,7 @@ class GraphRelationships {
explicit Iterator(mgp_vertices_iterator *nodes_iterator); explicit Iterator(mgp_vertices_iterator *nodes_iterator);
Iterator(const Iterator &other); Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete; Iterator &operator=(const Iterator &other) = delete;
~Iterator(); ~Iterator();
@ -253,7 +256,7 @@ class Relationships {
explicit Iterator(mgp_edges_iterator *relationships_iterator); explicit Iterator(mgp_edges_iterator *relationships_iterator);
Iterator(const Iterator &other); Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete; Iterator &operator=(const Iterator &other) = delete;
~Iterator(); ~Iterator();
@ -281,46 +284,12 @@ class Relationships {
mgp_edges_iterator *relationships_iterator_ = nullptr; mgp_edges_iterator *relationships_iterator_ = nullptr;
}; };
/// @brief View of node properties.
class Properties {
public:
explicit Properties(mgp_properties_iterator *properties_iterator);
/// @brief Returns the size of the properties map.
size_t Size() const;
/// @brief Returns whether the properties map is empty.
bool Empty() const;
/// @brief Returns the value associated with the given `key`. If theres no such value, the behavior is undefined.
/// @note Each key-value pair needs to be checked, ensuing O(n) time complexity.
Value operator[](const std::string_view key) const;
std::map<std::string_view, Value>::const_iterator begin() const;
std::map<std::string_view, Value>::const_iterator end() const;
std::map<std::string_view, Value>::const_iterator cbegin() const;
std::map<std::string_view, Value>::const_iterator cend() const;
/// @brief Returns the key-value iterator for the given `key`. If theres no such pair, returns the end of the
/// iterator.
/// @note Each key-value pair needs to be checked, ensuing O(n) time complexity.
std::map<std::string_view, Value>::const_iterator find(const std::string_view key) const;
/// @exception std::runtime_error Map contains value(s) of unknown type.
bool operator==(const Properties &other) const;
/// @exception std::runtime_error Map contains value(s) of unknown type.
bool operator!=(const Properties &other) const;
private:
std::map<const std::string_view, Value> property_map_;
};
/// @brief View of node labels. /// @brief View of node labels.
class Labels { class Labels {
public: public:
explicit Labels(mgp_vertex *node_ptr); explicit Labels(mgp_vertex *node_ptr);
Labels(const Labels &other); Labels(const Labels &other) noexcept;
Labels(Labels &&other) noexcept; Labels(Labels &&other) noexcept;
Labels &operator=(const Labels &other) noexcept; Labels &operator=(const Labels &other) noexcept;
@ -363,6 +332,7 @@ class Labels {
private: private:
mgp_vertex *node_ptr_; mgp_vertex *node_ptr_;
}; };
/* #endregion */ /* #endregion */
/* #region Types */ /* #region Types */
@ -397,7 +367,7 @@ class List {
/// @brief Creates a List from the given initializer_list. /// @brief Creates a List from the given initializer_list.
explicit List(const std::initializer_list<Value> list); explicit List(const std::initializer_list<Value> list);
List(const List &other); List(const List &other) noexcept;
List(List &&other) noexcept; List(List &&other) noexcept;
List &operator=(const List &other) noexcept; List &operator=(const List &other) noexcept;
@ -489,7 +459,7 @@ class Map {
/// @brief Creates a Map from the given initializer_list (map items correspond to initializer list pairs). /// @brief Creates a Map from the given initializer_list (map items correspond to initializer list pairs).
Map(const std::initializer_list<std::pair<std::string_view, Value>> items); Map(const std::initializer_list<std::pair<std::string_view, Value>> items);
Map(const Map &other); Map(const Map &other) noexcept;
Map(Map &&other) noexcept; Map(Map &&other) noexcept;
Map &operator=(const Map &other) noexcept; Map &operator=(const Map &other) noexcept;
@ -519,7 +489,7 @@ class Map {
explicit Iterator(mgp_map_items_iterator *map_items_iterator); explicit Iterator(mgp_map_items_iterator *map_items_iterator);
Iterator(const Iterator &other); Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete; Iterator &operator=(const Iterator &other) = delete;
~Iterator(); ~Iterator();
@ -578,7 +548,7 @@ class Node {
/// @brief Creates a Node from the copy of the given @ref mgp_vertex. /// @brief Creates a Node from the copy of the given @ref mgp_vertex.
explicit Node(const mgp_vertex *const_ptr); explicit Node(const mgp_vertex *const_ptr);
Node(const Node &other); Node(const Node &other) noexcept;
Node(Node &&other) noexcept; Node(Node &&other) noexcept;
Node &operator=(const Node &other) noexcept; Node &operator=(const Node &other) noexcept;
@ -590,16 +560,19 @@ class Node {
mgp::Id Id() const; mgp::Id Id() const;
/// @brief Returns an iterable & indexable structure of the nodes labels. /// @brief Returns an iterable & indexable structure of the nodes labels.
class Labels Labels() const; mgp::Labels Labels() const;
/// @brief Returns whether the node has the given `label`. /// @brief Returns whether the node has the given `label`.
bool HasLabel(std::string_view label) const; bool HasLabel(std::string_view label) const;
/// @brief Returns an iterable & indexable structure of the nodes properties. /// @brief Returns an std::map of the nodes properties.
class Properties Properties() const; std::map<std::string, Value> Properties() const;
/// @brief Returns the value of the nodes `property_name` property. /// @brief Sets the chosen property to the given value.
Value operator[](const std::string_view property_name) const; void SetProperty(std::string property, Value value);
/// @brief Retrieves the value of the chosen property.
Value GetProperty(const std::string &property) const;
/// @brief Returns an iterable structure of the nodes inbound relationships. /// @brief Returns an iterable structure of the nodes inbound relationships.
Relationships InRelationships() const; Relationships InRelationships() const;
@ -635,7 +608,7 @@ class Relationship {
/// @brief Creates a Relationship from the copy of the given @ref mgp_edge. /// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
explicit Relationship(const mgp_edge *const_ptr); explicit Relationship(const mgp_edge *const_ptr);
Relationship(const Relationship &other); Relationship(const Relationship &other) noexcept;
Relationship(Relationship &&other) noexcept; Relationship(Relationship &&other) noexcept;
Relationship &operator=(const Relationship &other) noexcept; Relationship &operator=(const Relationship &other) noexcept;
@ -649,11 +622,14 @@ class Relationship {
/// @brief Returns the relationships type. /// @brief Returns the relationships type.
std::string_view Type() const; std::string_view Type() const;
/// @brief Returns an iterable & indexable structure of the relationships properties. /// @brief Returns an std::map of the relationships properties.
class Properties Properties() const; std::map<std::string, Value> Properties() const;
/// @brief Returns the value of the relationships `property_name` property. /// @brief Sets the chosen property to the given value.
Value operator[](const std::string_view property_name) const; void SetProperty(std::string property, Value value);
/// @brief Retrieves the value of the chosen property.
Value GetProperty(const std::string &property) const;
/// @brief Returns the relationships source node. /// @brief Returns the relationships source node.
Node From() const; Node From() const;
@ -688,7 +664,7 @@ class Path {
/// @brief Creates a Path starting with the given `start_node`. /// @brief Creates a Path starting with the given `start_node`.
explicit Path(const Node &start_node); explicit Path(const Node &start_node);
Path(const Path &other); Path(const Path &other) noexcept;
Path(Path &&other) noexcept; Path(Path &&other) noexcept;
Path &operator=(const Path &other) noexcept; Path &operator=(const Path &other) noexcept;
@ -744,7 +720,7 @@ class Date {
/// @brief Creates a Date object with the given `year`, `month`, and `day` properties. /// @brief Creates a Date object with the given `year`, `month`, and `day` properties.
Date(int year, int month, int day); Date(int year, int month, int day);
Date(const Date &other); Date(const Date &other) noexcept;
Date(Date &&other) noexcept; Date(Date &&other) noexcept;
Date &operator=(const Date &other) noexcept; Date &operator=(const Date &other) noexcept;
@ -799,7 +775,7 @@ class LocalTime {
/// properties. /// properties.
LocalTime(int hour, int minute, int second, int millisecond, int microsecond); LocalTime(int hour, int minute, int second, int millisecond, int microsecond);
LocalTime(const LocalTime &other); LocalTime(const LocalTime &other) noexcept;
LocalTime(LocalTime &&other) noexcept; LocalTime(LocalTime &&other) noexcept;
LocalTime &operator=(const LocalTime &other) noexcept; LocalTime &operator=(const LocalTime &other) noexcept;
@ -858,7 +834,7 @@ class LocalDateTime {
/// `millisecond`, and `microsecond` properties. /// `millisecond`, and `microsecond` properties.
LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond); LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);
LocalDateTime(const LocalDateTime &other); LocalDateTime(const LocalDateTime &other) noexcept;
LocalDateTime(LocalDateTime &&other) noexcept; LocalDateTime(LocalDateTime &&other) noexcept;
LocalDateTime &operator=(const LocalDateTime &other) noexcept; LocalDateTime &operator=(const LocalDateTime &other) noexcept;
@ -929,7 +905,7 @@ class Duration {
/// `microsecond` properties. /// `microsecond` properties.
Duration(double day, double hour, double minute, double second, double millisecond, double microsecond); Duration(double day, double hour, double minute, double second, double millisecond, double microsecond);
Duration(const Duration &other); Duration(const Duration &other) noexcept;
Duration(Duration &&other) noexcept; Duration(Duration &&other) noexcept;
Duration &operator=(const Duration &other) noexcept; Duration &operator=(const Duration &other) noexcept;
@ -986,6 +962,8 @@ class Value {
explicit Value(mgp_value *ptr); explicit Value(mgp_value *ptr);
explicit Value(StealType /*steal*/, mgp_value *ptr);
// Null constructor: // Null constructor:
explicit Value(); explicit Value();
@ -1056,7 +1034,7 @@ class Value {
/// @note The behavior of accessing `duration` after performing this operation is undefined. /// @note The behavior of accessing `duration` after performing this operation is undefined.
explicit Value(Duration &&duration); explicit Value(Duration &&duration);
Value(const Value &other); Value(const Value &other) noexcept;
Value(Value &&other) noexcept; Value(Value &&other) noexcept;
Value &operator=(const Value &other) noexcept; Value &operator=(const Value &other) noexcept;
@ -1704,7 +1682,7 @@ inline Nodes::Iterator::Iterator(mgp_vertices_iterator *nodes_iterator) : nodes_
} }
} }
inline Nodes::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {} inline Nodes::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.nodes_iterator_) {}
inline Nodes::Iterator::~Iterator() { inline Nodes::Iterator::~Iterator() {
if (nodes_iterator_ != nullptr) { if (nodes_iterator_ != nullptr) {
@ -1795,7 +1773,7 @@ inline GraphRelationships::Iterator::Iterator(mgp_vertices_iterator *nodes_itera
} }
} }
inline GraphRelationships::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {} inline GraphRelationships::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.nodes_iterator_) {}
inline GraphRelationships::Iterator::~Iterator() { inline GraphRelationships::Iterator::~Iterator() {
if (nodes_iterator_ != nullptr) { if (nodes_iterator_ != nullptr) {
@ -1814,10 +1792,12 @@ inline GraphRelationships::Iterator &GraphRelationships::Iterator::operator++()
if (out_relationships_iterator_ != nullptr) { if (out_relationships_iterator_ != nullptr) {
auto next = mgp::edges_iterator_next(out_relationships_iterator_); auto next = mgp::edges_iterator_next(out_relationships_iterator_);
if (next == nullptr) { if (next != nullptr) {
mgp::edges_iterator_destroy(out_relationships_iterator_); return *this;
out_relationships_iterator_ = nullptr;
} }
mgp::edges_iterator_destroy(out_relationships_iterator_);
out_relationships_iterator_ = nullptr;
} }
// 2. Move onto the next nodes // 2. Move onto the next nodes
@ -1904,7 +1884,7 @@ inline Relationships::Iterator::Iterator(mgp_edges_iterator *relationships_itera
} }
} }
inline Relationships::Iterator::Iterator(const Iterator &other) : Iterator(other.relationships_iterator_) {} inline Relationships::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.relationships_iterator_) {}
inline Relationships::Iterator::~Iterator() { inline Relationships::Iterator::~Iterator() {
if (relationships_iterator_ != nullptr) { if (relationships_iterator_ != nullptr) {
@ -1963,40 +1943,11 @@ inline Relationships::Iterator Relationships::cbegin() const { return Iterator(r
inline Relationships::Iterator Relationships::cend() const { return Iterator(nullptr); } inline Relationships::Iterator Relationships::cend() const { return Iterator(nullptr); }
// Properties:
inline Properties::Properties(mgp_properties_iterator *properties_iterator) {
for (auto property = mgp::properties_iterator_get(properties_iterator); property;
property = mgp::properties_iterator_next(properties_iterator)) {
auto value = Value(property->value);
property_map_.emplace(property->name, value);
}
mgp::properties_iterator_destroy(properties_iterator);
}
inline size_t Properties::Size() const { return property_map_.size(); }
inline bool Properties::Empty() const { return Size() == 0; }
inline Value Properties::operator[](const std::string_view key) const { return property_map_.at(key); }
inline std::map<std::string_view, Value>::const_iterator Properties::begin() const { return property_map_.begin(); }
inline std::map<std::string_view, Value>::const_iterator Properties::end() const { return property_map_.end(); }
inline std::map<std::string_view, Value>::const_iterator Properties::cbegin() const { return property_map_.cbegin(); }
inline std::map<std::string_view, Value>::const_iterator Properties::cend() const { return property_map_.cend(); }
inline bool Properties::operator==(const Properties &other) const { return property_map_ == other.property_map_; }
inline bool Properties::operator!=(const Properties &other) const { return !(*this == other); }
// Labels: // Labels:
inline Labels::Labels(mgp_vertex *node_ptr) : node_ptr_(mgp::vertex_copy(node_ptr, memory)) {} inline Labels::Labels(mgp_vertex *node_ptr) : node_ptr_(mgp::vertex_copy(node_ptr, memory)) {}
inline Labels::Labels(const Labels &other) : Labels(other.node_ptr_) {} inline Labels::Labels(const Labels &other) noexcept : Labels(other.node_ptr_) {}
inline Labels::Labels(Labels &&other) noexcept : node_ptr_(other.node_ptr_) { other.node_ptr_ = nullptr; } inline Labels::Labels(Labels &&other) noexcept : node_ptr_(other.node_ptr_) { other.node_ptr_ = nullptr; }
@ -2086,7 +2037,7 @@ inline List::List(const std::initializer_list<Value> values) : ptr_(mgp::list_ma
} }
} }
inline List::List(const List &other) : List(other.ptr_) {} inline List::List(const List &other) noexcept : List(other.ptr_) {}
inline List::List(List &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline List::List(List &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2194,7 +2145,7 @@ inline Map::Map(const std::initializer_list<std::pair<std::string_view, Value>>
} }
} }
inline Map::Map(const Map &other) : Map(other.ptr_) {} inline Map::Map(const Map &other) noexcept : Map(other.ptr_) {}
inline Map::Map(Map &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Map::Map(Map &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2239,7 +2190,7 @@ inline Map::Iterator::Iterator(mgp_map_items_iterator *map_items_iterator) : map
} }
} }
inline Map::Iterator::Iterator(const Iterator &other) : Iterator(other.map_items_iterator_) {} inline Map::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.map_items_iterator_) {}
inline Map::Iterator::~Iterator() { inline Map::Iterator::~Iterator() {
if (map_items_iterator_ != nullptr) { if (map_items_iterator_ != nullptr) {
@ -2306,10 +2257,6 @@ inline void Map::Insert(std::string_view key, Value &&value) {
value.ptr_ = nullptr; value.ptr_ = nullptr;
} }
inline std::map<std::string_view, Value>::const_iterator Properties::find(const std::string_view key) const {
return property_map_.find(key);
}
inline bool Map::operator==(const Map &other) const { return util::MapsEqual(ptr_, other.ptr_); } inline bool Map::operator==(const Map &other) const { return util::MapsEqual(ptr_, other.ptr_); }
inline bool Map::operator!=(const Map &other) const { return !(*this == other); } inline bool Map::operator!=(const Map &other) const { return !(*this == other); }
@ -2324,7 +2271,7 @@ inline Node::Node(mgp_vertex *ptr) : ptr_(mgp::vertex_copy(ptr, memory)) {}
inline Node::Node(const mgp_vertex *const_ptr) : ptr_(mgp::vertex_copy(const_cast<mgp_vertex *>(const_ptr), memory)) {} inline Node::Node(const mgp_vertex *const_ptr) : ptr_(mgp::vertex_copy(const_cast<mgp_vertex *>(const_ptr), memory)) {}
inline Node::Node(const Node &other) : Node(other.ptr_) {} inline Node::Node(const Node &other) noexcept : Node(other.ptr_) {}
inline Node::Node(Node &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Node::Node(Node &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2355,7 +2302,7 @@ inline Node::~Node() {
inline mgp::Id Node::Id() const { return Id::FromInt(mgp::vertex_get_id(ptr_).as_int); } inline mgp::Id Node::Id() const { return Id::FromInt(mgp::vertex_get_id(ptr_).as_int); }
inline class Labels Node::Labels() const { return mgp::Labels(ptr_); } inline mgp::Labels Node::Labels() const { return mgp::Labels(ptr_); }
inline bool Node::HasLabel(std::string_view label) const { inline bool Node::HasLabel(std::string_view label) const {
for (const auto node_label : Labels()) { for (const auto node_label : Labels()) {
@ -2366,10 +2313,6 @@ inline bool Node::HasLabel(std::string_view label) const {
return false; return false;
} }
inline class Properties Node::Properties() const { return mgp::Properties(mgp::vertex_iter_properties(ptr_, memory)); }
inline Value Node::operator[](const std::string_view property_name) const { return Properties()[property_name]; }
inline Relationships Node::InRelationships() const { inline Relationships Node::InRelationships() const {
auto relationship_iterator = mgp::vertex_iter_in_edges(ptr_, memory); auto relationship_iterator = mgp::vertex_iter_in_edges(ptr_, memory);
if (relationship_iterator == nullptr) { if (relationship_iterator == nullptr) {
@ -2390,6 +2333,26 @@ inline void Node::AddLabel(const std::string_view label) {
mgp::vertex_add_label(this->ptr_, mgp_label{.name = label.data()}); mgp::vertex_add_label(this->ptr_, mgp_label{.name = label.data()});
} }
inline std::map<std::string, Value> Node::Properties() const {
mgp_properties_iterator *properties_iterator = mgp::vertex_iter_properties(ptr_, memory);
std::map<std::string, Value> property_map;
for (auto *property = mgp::properties_iterator_get(properties_iterator); property;
property = mgp::properties_iterator_next(properties_iterator)) {
property_map.emplace(std::string(property->name), Value(property->value));
}
mgp::properties_iterator_destroy(properties_iterator);
return property_map;
}
inline void Node::SetProperty(std::string property, Value value) {
mgp::vertex_set_property(ptr_, property.data(), value.ptr());
}
inline Value Node::GetProperty(const std::string &property) const {
mgp_value *vertex_prop = mgp::vertex_get_property(ptr_, property.data(), memory);
return Value(steal, vertex_prop);
}
inline bool Node::operator<(const Node &other) const { return Id() < other.Id(); } inline bool Node::operator<(const Node &other) const { return Id() < other.Id(); }
inline bool Node::operator==(const Node &other) const { return util::NodesEqual(ptr_, other.ptr_); } inline bool Node::operator==(const Node &other) const { return util::NodesEqual(ptr_, other.ptr_); }
@ -2403,7 +2366,7 @@ inline Relationship::Relationship(mgp_edge *ptr) : ptr_(mgp::edge_copy(ptr, memo
inline Relationship::Relationship(const mgp_edge *const_ptr) inline Relationship::Relationship(const mgp_edge *const_ptr)
: ptr_(mgp::edge_copy(const_cast<mgp_edge *>(const_ptr), memory)) {} : ptr_(mgp::edge_copy(const_cast<mgp_edge *>(const_ptr), memory)) {}
inline Relationship::Relationship(const Relationship &other) : Relationship(other.ptr_) {} inline Relationship::Relationship(const Relationship &other) noexcept : Relationship(other.ptr_) {}
inline Relationship::Relationship(Relationship &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Relationship::Relationship(Relationship &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2436,12 +2399,24 @@ inline mgp::Id Relationship::Id() const { return Id::FromInt(mgp::edge_get_id(pt
inline std::string_view Relationship::Type() const { return mgp::edge_get_type(ptr_).name; } inline std::string_view Relationship::Type() const { return mgp::edge_get_type(ptr_).name; }
inline class Properties Relationship::Properties() const { inline std::map<std::string, Value> Relationship::Properties() const {
return mgp::Properties(mgp::edge_iter_properties(ptr_, memory)); mgp_properties_iterator *properties_iterator = mgp::edge_iter_properties(ptr_, memory);
std::map<std::string, Value> property_map;
for (mgp_property *property = mgp::properties_iterator_get(properties_iterator); property;
property = mgp::properties_iterator_next(properties_iterator)) {
property_map.emplace(property->name, Value(property->value));
}
mgp::properties_iterator_destroy(properties_iterator);
return property_map;
} }
inline Value Relationship::operator[](const std::string_view property_name) const { inline void Relationship::SetProperty(std::string property, Value value) {
return Properties()[property_name]; mgp::edge_set_property(ptr_, property.data(), value.ptr());
}
inline Value Relationship::GetProperty(const std::string &property) const {
mgp_value *edge_prop = mgp::edge_get_property(ptr_, property.data(), memory);
return Value(steal, edge_prop);
} }
inline Node Relationship::From() const { return Node(mgp::edge_get_from(ptr_)); } inline Node Relationship::From() const { return Node(mgp::edge_get_from(ptr_)); }
@ -2464,7 +2439,7 @@ inline Path::Path(const mgp_path *const_ptr) : ptr_(mgp::path_copy(const_cast<mg
inline Path::Path(const Node &start_node) : ptr_(mgp::path_make_with_start(start_node.ptr_, memory)) {} inline Path::Path(const Node &start_node) : ptr_(mgp::path_make_with_start(start_node.ptr_, memory)) {}
inline Path::Path(const Path &other) : Path(other.ptr_) {} inline Path::Path(const Path &other) noexcept : Path(other.ptr_) {}
inline Path::Path(Path &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Path::Path(Path &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2533,7 +2508,7 @@ inline Date::Date(int year, int month, int day) {
ptr_ = mgp::date_from_parameters(&params, memory); ptr_ = mgp::date_from_parameters(&params, memory);
} }
inline Date::Date(const Date &other) : Date(other.ptr_) {} inline Date::Date(const Date &other) noexcept : Date(other.ptr_) {}
inline Date::Date(Date &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Date::Date(Date &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
@ -2627,7 +2602,7 @@ inline LocalTime::LocalTime(int hour, int minute, int second, int millisecond, i
ptr_ = mgp::local_time_from_parameters(&params, memory); ptr_ = mgp::local_time_from_parameters(&params, memory);
} }
inline LocalTime::LocalTime(const LocalTime &other) : LocalTime(other.ptr_) {} inline LocalTime::LocalTime(const LocalTime &other) noexcept : LocalTime(other.ptr_) {}
inline LocalTime::LocalTime(LocalTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }; inline LocalTime::LocalTime(LocalTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
@ -2732,7 +2707,7 @@ inline LocalDateTime::LocalDateTime(int year, int month, int day, int hour, int
ptr_ = mgp::local_date_time_from_parameters(&params, memory); ptr_ = mgp::local_date_time_from_parameters(&params, memory);
} }
inline LocalDateTime::LocalDateTime(const LocalDateTime &other) : LocalDateTime(other.ptr_) {} inline LocalDateTime::LocalDateTime(const LocalDateTime &other) noexcept : LocalDateTime(other.ptr_) {}
inline LocalDateTime::LocalDateTime(LocalDateTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }; inline LocalDateTime::LocalDateTime(LocalDateTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
@ -2845,7 +2820,7 @@ inline Duration::Duration(double day, double hour, double minute, double second,
ptr_ = mgp::duration_from_parameters(&params, memory); ptr_ = mgp::duration_from_parameters(&params, memory);
} }
inline Duration::Duration(const Duration &other) : Duration(other.ptr_) {} inline Duration::Duration(const Duration &other) noexcept : Duration(other.ptr_) {}
inline Duration::Duration(Duration &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }; inline Duration::Duration(Duration &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
@ -2917,6 +2892,7 @@ inline bool Duration::operator<(const Duration &other) const {
/* #region Value */ /* #region Value */
inline Value::Value(mgp_value *ptr) : ptr_(mgp::value_copy(ptr, memory)) {} inline Value::Value(mgp_value *ptr) : ptr_(mgp::value_copy(ptr, memory)) {}
inline Value::Value(StealType /*steal*/, mgp_value *ptr) : ptr_{ptr} {}
inline Value::Value() : ptr_(mgp::value_make_null(memory)) {} inline Value::Value() : ptr_(mgp::value_make_null(memory)) {}
@ -2997,7 +2973,7 @@ inline Value::Value(Duration &&duration) {
duration.ptr_ = nullptr; duration.ptr_ = nullptr;
} }
inline Value::Value(const Value &other) : Value(other.ptr_) {} inline Value::Value(const Value &other) noexcept : Value(other.ptr_) {}
inline Value::Value(Value &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } inline Value::Value(Value &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

2
init
View File

@ -149,7 +149,7 @@ python3 -m pre_commit install
# Install py format tools # Install py format tools
echo "Install black formatter" echo "Install black formatter"
python3 -m pip install black==22.10.* python3 -m pip install black==22.8.*
echo "Install isort" echo "Install isort"
python3 -m pip install isort==5.10.* python3 -m pip install isort==5.10.*

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 3. using the Licensed Work to create a work or solution
which competes (or might reasonably be expected to which competes (or might reasonably be expected to
compete) with the Licensed Work. compete) with the Licensed Work.
CHANGE DATE: 2026-13-12 CHANGE DATE: 2027-19-01
CHANGE LICENSE: Apache License, Version 2.0 CHANGE LICENSE: Apache License, Version 2.0
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal. For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.

View File

@ -146,9 +146,10 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
throw ServerMalformedDataException(); throw ServerMalformedDataException();
} }
auto &header = fields.ValueMap();
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())}; QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
auto &header = fields.ValueMap();
if (header.find("fields") == header.end()) { if (header.find("fields") == header.end()) {
throw ServerMalformedDataException(); throw ServerMalformedDataException();
} }
@ -164,6 +165,10 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
ret.fields.emplace_back(std::move(field_item.ValueString())); ret.fields.emplace_back(std::move(field_item.ValueString()));
} }
if (header.contains("qid")) {
ret.metadata["qid"] = header["qid"];
}
return ret; return ret;
} }

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -73,40 +73,6 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
namespace details { namespace details {
template <typename TSession>
State HandleRun(TSession &session, const State state, const Value &query, const Value &params) {
if (state != State::Idle) {
// Client could potentially recover if we move to error state, but there is
// no legitimate situation in which well working client would end up in this
// situation.
spdlog::trace("Unexpected RUN command!");
return State::Close;
}
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
spdlog::debug("[Run] '{}'", query.ValueString());
try {
// Interpret can throw.
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
// Convert std::string to Value
std::vector<Value> vec;
std::map<std::string, Value> data;
vec.reserve(header.size());
for (auto &i : header) vec.emplace_back(std::move(i));
data.emplace("fields", std::move(vec));
// Send the header.
if (!session.encoder_.MessageSuccess(data)) {
spdlog::trace("Couldn't send query header!");
return State::Close;
}
return State::Result;
} catch (const std::exception &e) {
return HandleFailure(session, e);
}
}
template <bool is_pull, typename TSession> template <bool is_pull, typename TSession>
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) { State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
try { try {
@ -229,7 +195,36 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
return State::Close; return State::Close;
} }
return details::HandleRun(session, state, query, params); if (state != State::Idle) {
// Client could potentially recover if we move to error state, but there is
// no legitimate situation in which well working client would end up in this
// situation.
spdlog::trace("Unexpected RUN command!");
return State::Close;
}
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
spdlog::debug("[Run] '{}'", query.ValueString());
try {
// Interpret can throw.
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
// Convert std::string to Value
std::vector<Value> vec;
std::map<std::string, Value> data;
vec.reserve(header.size());
for (auto &i : header) vec.emplace_back(std::move(i));
data.emplace("fields", std::move(vec));
// Send the header.
if (!session.encoder_.MessageSuccess(data)) {
spdlog::trace("Couldn't send query header!");
return State::Close;
}
return State::Result;
} catch (const std::exception &e) {
return HandleFailure(session, e);
}
} }
template <typename TSession> template <typename TSession>
@ -257,7 +252,40 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
spdlog::trace("Couldn't read extra field!"); spdlog::trace("Couldn't read extra field!");
} }
return details::HandleRun(session, state, query, params); if (state != State::Idle) {
// Client could potentially recover if we move to error state, but there is
// no legitimate situation in which well working client would end up in this
// situation.
spdlog::trace("Unexpected RUN command!");
return State::Close;
}
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
spdlog::debug("[Run] '{}'", query.ValueString());
try {
// Interpret can throw.
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
// Convert std::string to Value
std::vector<Value> vec;
std::map<std::string, Value> data;
vec.reserve(header.size());
for (auto &i : header) vec.emplace_back(std::move(i));
data.emplace("fields", std::move(vec));
if (qid.has_value()) {
data.emplace("qid", Value{*qid});
}
// Send the header.
if (!session.encoder_.MessageSuccess(data)) {
spdlog::trace("Couldn't send query header!");
return State::Close;
}
return State::Result;
} catch (const std::exception &e) {
return HandleFailure(session, e);
}
} }
template <typename TSession> template <typename TSession>

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -64,14 +64,6 @@ auto SymbolGenerator::CreateSymbol(const std::string &name, bool user_declared,
return symbol; return symbol;
} }
auto SymbolGenerator::GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type) {
auto &scope = scopes_.back();
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
return *maybe_symbol;
}
return CreateSymbol(name, user_declared, type);
}
auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) { auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) {
// NOLINTNEXTLINE // NOLINTNEXTLINE
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) { for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
@ -206,7 +198,7 @@ bool SymbolGenerator::PreVisit(CallProcedure &call_proc) {
bool SymbolGenerator::PostVisit(CallProcedure &call_proc) { bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
for (auto *ident : call_proc.result_identifiers_) { for (auto *ident : call_proc.result_identifiers_) {
if (HasSymbolLocalScope(ident->name_)) { if (HasSymbol(ident->name_)) {
throw RedeclareVariableError(ident->name_); throw RedeclareVariableError(ident->name_);
} }
ident->MapTo(CreateSymbol(ident->name_, true)); ident->MapTo(CreateSymbol(ident->name_, true));
@ -217,7 +209,7 @@ bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
bool SymbolGenerator::PreVisit(LoadCsv &load_csv) { return false; } bool SymbolGenerator::PreVisit(LoadCsv &load_csv) { return false; }
bool SymbolGenerator::PostVisit(LoadCsv &load_csv) { bool SymbolGenerator::PostVisit(LoadCsv &load_csv) {
if (HasSymbolLocalScope(load_csv.row_var_->name_)) { if (HasSymbol(load_csv.row_var_->name_)) {
throw RedeclareVariableError(load_csv.row_var_->name_); throw RedeclareVariableError(load_csv.row_var_->name_);
} }
load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true)); load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true));
@ -265,7 +257,7 @@ bool SymbolGenerator::PostVisit(Merge &) {
bool SymbolGenerator::PostVisit(Unwind &unwind) { bool SymbolGenerator::PostVisit(Unwind &unwind) {
const auto &name = unwind.named_expression_->name_; const auto &name = unwind.named_expression_->name_;
if (HasSymbolLocalScope(name)) { if (HasSymbol(name)) {
throw RedeclareVariableError(name); throw RedeclareVariableError(name);
} }
unwind.named_expression_->MapTo(CreateSymbol(name, true)); unwind.named_expression_->MapTo(CreateSymbol(name, true));
@ -282,7 +274,7 @@ bool SymbolGenerator::PostVisit(Match &) {
// Check variables in property maps after visiting Match, so that they can // Check variables in property maps after visiting Match, so that they can
// reference symbols out of bind order. // reference symbols out of bind order.
for (auto &ident : scope.identifiers_in_match) { for (auto &ident : scope.identifiers_in_match) {
if (!HasSymbolLocalScope(ident->name_) && !ConsumePredefinedIdentifier(ident->name_)) if (!HasSymbol(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
throw UnboundVariableError(ident->name_); throw UnboundVariableError(ident->name_);
ident->MapTo(scope.symbols[ident->name_]); ident->MapTo(scope.symbols[ident->name_]);
} }
@ -314,7 +306,7 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) { if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) {
// If we are in the pattern, and outside of a node or an edge, the // If we are in the pattern, and outside of a node or an edge, the
// identifier is the pattern name. // identifier is the pattern name.
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH); symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::PATH);
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) { } else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
// Patterns used to create nodes and edges cannot redeclare already // Patterns used to create nodes and edges cannot redeclare already
// established bindings. Declaration only happens in single node // established bindings. Declaration only happens in single node
@ -322,19 +314,19 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
// `MATCH (n) CREATE (n)` should throw an error that `n` is already // `MATCH (n) CREATE (n)` should throw an error that `n` is already
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed, // declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
// since `n` now references the bound node instead of declaring it. // since `n` now references the bound node instead of declaring it.
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) { if ((scope.in_create_node || scope.in_create_edge) && HasSymbol(ident.name_)) {
throw RedeclareVariableError(ident.name_); throw RedeclareVariableError(ident.name_);
} }
auto type = Symbol::Type::VERTEX; auto type = Symbol::Type::VERTEX;
if (scope.visiting_edge) { if (scope.visiting_edge) {
// Edge referencing is not allowed (like in Neo4j): // Edge referencing is not allowed (like in Neo4j):
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed. // `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
if (HasSymbolLocalScope(ident.name_)) { if (HasSymbol(ident.name_)) {
throw RedeclareVariableError(ident.name_); throw RedeclareVariableError(ident.name_);
} }
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE; type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
} }
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, type); symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) { } else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) { if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) {
// Prevent variable path bounds to reference the identifier which is bound // Prevent variable path bounds to reference the identifier which is bound
@ -461,7 +453,7 @@ bool SymbolGenerator::PreVisit(NodeAtom &node_atom) {
auto &scope = scopes_.back(); auto &scope = scopes_.back();
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) { auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
const auto &node_name = node_atom.identifier_->name_; const auto &node_name = node_atom.identifier_->name_;
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) { if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbol(node_name)) {
throw SemanticException("Cannot create node '" + node_name + throw SemanticException("Cannot create node '" + node_name +
"' with labels or properties, because it is already declared."); "' with labels or properties, because it is already declared.");
} }
@ -555,11 +547,11 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
edge_atom.identifier_->Accept(*this); edge_atom.identifier_->Accept(*this);
scope.in_pattern_atom_identifier = false; scope.in_pattern_atom_identifier = false;
if (edge_atom.total_weight_) { if (edge_atom.total_weight_) {
if (HasSymbolLocalScope(edge_atom.total_weight_->name_)) { if (HasSymbol(edge_atom.total_weight_->name_)) {
throw RedeclareVariableError(edge_atom.total_weight_->name_); throw RedeclareVariableError(edge_atom.total_weight_->name_);
} }
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope( edge_atom.total_weight_->MapTo(GetOrCreateSymbol(edge_atom.total_weight_->name_,
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER)); edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
} }
return false; return false;
} }
@ -602,10 +594,6 @@ bool SymbolGenerator::HasSymbol(const std::string &name) const {
return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); }); return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); });
} }
bool SymbolGenerator::HasSymbolLocalScope(const std::string &name) const {
return scopes_.back().symbols.contains(name);
}
bool SymbolGenerator::ConsumePredefinedIdentifier(const std::string &name) { bool SymbolGenerator::ConsumePredefinedIdentifier(const std::string &name) {
auto it = predefined_identifiers_.find(name); auto it = predefined_identifiers_.find(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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -134,7 +134,6 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope, Symbol::Type type); static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope, Symbol::Type type);
bool HasSymbol(const std::string &name) const; bool HasSymbol(const std::string &name) const;
bool HasSymbolLocalScope(const std::string &name) const;
// @return true if it added a predefined identifier with that name // @return true if it added a predefined identifier with that name
bool ConsumePredefinedIdentifier(const std::string &name); bool ConsumePredefinedIdentifier(const std::string &name);
@ -147,7 +146,6 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY); auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
// Returns the symbol by name. If the mapping already exists, checks if the // Returns the symbol by name. If the mapping already exists, checks if the
// types match. Otherwise, returns a new symbol. // types match. Otherwise, returns a new symbol.
auto GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
void VisitReturnBody(ReturnBody &body, Where *where = nullptr); void VisitReturnBody(ReturnBody &body, Where *where = nullptr);

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -2020,7 +2020,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
next_edges_.clear(); next_edges_.clear();
traversal_stack_.clear(); traversal_stack_.clear();
pq_.push({TypedValue(), 0, *start_vertex, std::nullopt}); expand_from_vertex(*start_vertex, TypedValue(), 0);
visited_cost_.emplace(*start_vertex, 0); visited_cost_.emplace(*start_vertex, 0);
frame[self_.common_.edge_symbol] = TypedValue::TVector(memory); frame[self_.common_.edge_symbol] = TypedValue::TVector(memory);
} }
@ -2029,33 +2029,28 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
while (!pq_.empty()) { while (!pq_.empty()) {
if (MustAbort(context)) throw HintedAbortError(); if (MustAbort(context)) throw HintedAbortError();
auto [current_weight, current_depth, current_vertex, maybe_directed_edge] = pq_.top(); const auto [current_weight, current_depth, current_vertex, directed_edge] = pq_.top();
pq_.pop(); pq_.pop();
const auto &[current_edge, direction, weight] = directed_edge;
if (expanded_.contains(current_edge)) continue;
expanded_.emplace(current_edge);
// Expand only if what we've just expanded is less than max depth. // Expand only if what we've just expanded is less than max depth.
if (current_depth < upper_bound_) { if (current_depth < upper_bound_) {
if (maybe_directed_edge) {
auto &[current_edge, direction, weight] = *maybe_directed_edge;
if (expanded_.find(current_edge) != expanded_.end()) continue;
expanded_.emplace(current_edge);
}
expand_from_vertex(current_vertex, current_weight, current_depth); expand_from_vertex(current_vertex, current_weight, current_depth);
} }
// if current vertex is not starting vertex, maybe_directed_edge will not be nullopt // Searching for a previous vertex in the expansion
if (maybe_directed_edge) { auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
auto &[current_edge, direction, weight] = *maybe_directed_edge;
// Searching for a previous vertex in the expansion
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
// Update the parent // Update the parent
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) { if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
utils::pmr::list<DirectedEdge> empty(memory); utils::pmr::list<DirectedEdge> empty(memory);
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty); next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
}
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(*maybe_directed_edge);
} }
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(directed_edge);
} }
if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) { if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) {
@ -2112,8 +2107,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Priority queue comparator. Keep lowest weight on top of the queue. // Priority queue comparator. Keep lowest weight on top of the queue.
class PriorityQueueComparator { class PriorityQueueComparator {
public: public:
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &lhs, bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &lhs,
const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &rhs) { const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &rhs) {
const auto &lhs_weight = std::get<0>(lhs); const auto &lhs_weight = std::get<0>(lhs);
const auto &rhs_weight = std::get<0>(rhs); const auto &rhs_weight = std::get<0>(rhs);
// Null defines minimum value for all types // Null defines minimum value for all types
@ -2132,8 +2127,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Priority queue - core element of the algorithm. // Priority queue - core element of the algorithm.
// Stores: {weight, depth, next vertex, edge and direction} // Stores: {weight, depth, next vertex, edge and direction}
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>, std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge>,
utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>>, utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge>>,
PriorityQueueComparator> PriorityQueueComparator>
pq_; pq_;

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -444,6 +444,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
bool PreVisit(Foreach &op) override { bool PreVisit(Foreach &op) override {
prev_ops_.push_back(&op); prev_ops_.push_back(&op);
op.input()->Accept(*this);
RewriteBranch(&op.update_clauses_);
return false; return false;
} }

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -174,7 +174,11 @@ class RuleBasedPlanner {
input_op = PlanMatching(match_ctx, std::move(input_op)); input_op = PlanMatching(match_ctx, std::move(input_op));
for (const auto &matching : query_part.optional_matching) { for (const auto &matching : query_part.optional_matching) {
MatchContext opt_ctx{matching, *context.symbol_table, context.bound_symbols}; MatchContext opt_ctx{matching, *context.symbol_table, context.bound_symbols};
auto match_op = PlanMatching(opt_ctx, nullptr);
std::vector<Symbol> bound_symbols(context_->bound_symbols.begin(), context_->bound_symbols.end());
auto once_with_symbols = std::make_unique<Once>(bound_symbols);
auto match_op = PlanMatching(opt_ctx, std::move(once_with_symbols));
if (match_op) { if (match_op) {
input_op = std::make_unique<Optional>(std::move(input_op), std::move(match_op), opt_ctx.new_symbols); input_op = std::make_unique<Optional>(std::move(input_op), std::move(match_op), opt_ctx.new_symbols);
} }

View File

@ -12,6 +12,7 @@
#include "storage/v2/edge_accessor.hpp" #include "storage/v2/edge_accessor.hpp"
#include <memory> #include <memory>
#include <tuple>
#include "storage/v2/mvcc.hpp" #include "storage/v2/mvcc.hpp"
#include "storage/v2/property_value.hpp" #include "storage/v2/property_value.hpp"
@ -21,8 +22,47 @@
namespace memgraph::storage { namespace memgraph::storage {
bool EdgeAccessor::IsVisible(const View view) const { bool EdgeAccessor::IsVisible(const View view) const {
bool deleted = true;
bool exists = true; bool exists = true;
bool deleted = true;
// When edges don't have properties, their isolation level is still dictated by MVCC ->
// iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges.
if (!config_.properties_on_edges) {
Delta *delta = nullptr;
{
std::lock_guard<utils::SpinLock> guard(from_vertex_->lock);
// Initialize deleted by checking if out edges contain edge_
deleted = std::find_if(from_vertex_->out_edges.begin(), from_vertex_->out_edges.end(), [&](const auto &out_edge) {
return std::get<2>(out_edge) == edge_;
}) == from_vertex_->out_edges.end();
delta = from_vertex_->delta;
}
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
switch (delta.action) {
case Delta::Action::ADD_LABEL:
case Delta::Action::REMOVE_LABEL:
case Delta::Action::SET_PROPERTY:
case Delta::Action::REMOVE_IN_EDGE:
case Delta::Action::ADD_IN_EDGE:
case Delta::Action::RECREATE_OBJECT:
case Delta::Action::DELETE_OBJECT:
break;
case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge
if (delta.vertex_edge.edge == edge_) {
deleted = false;
}
break;
}
case Delta::Action::REMOVE_OUT_EDGE: { // also relevant for the from_vertex_ -> we just added the edge
if (delta.vertex_edge.edge == edge_) {
exists = false;
}
break;
}
}
});
return exists && (for_deleted_ || !deleted);
}
Delta *delta = nullptr; Delta *delta = nullptr;
{ {
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock); std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
@ -49,7 +89,6 @@ bool EdgeAccessor::IsVisible(const View view) const {
} }
} }
}); });
return exists && (for_deleted_ || !deleted); return exists && (for_deleted_ || !deleted);
} }

View File

@ -20,3 +20,10 @@ add_subdirectory(procedures)
add_dependencies(memgraph__e2e__triggers__on_create memgraph__e2e__triggers__write.py) add_dependencies(memgraph__e2e__triggers__on_create memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_update memgraph__e2e__triggers__write.py) add_dependencies(memgraph__e2e__triggers__on_update memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers__write.py) add_dependencies(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers__write.py)
function(copy_triggers_e2e_python_files FILE_NAME)
copy_e2e_python_files(triggers ${FILE_NAME})
endfunction()
copy_triggers_e2e_python_files(common.py)
copy_triggers_e2e_python_files(triggers_properties_false.py)

View File

@ -0,0 +1,34 @@
# 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 typing
import mgclient
import pytest
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
@pytest.fixture
def connect(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
triggers_list = execute_and_fetch_all(connection.cursor(), "SHOW TRIGGERS;")
for trigger in triggers_list:
execute_and_fetch_all(connection.cursor(), f"DROP TRIGGER {trigger[0]}")
execute_and_fetch_all(connection.cursor(), "MATCH (n) DETACH DELETE n")
yield connection
for trigger in triggers_list:
execute_and_fetch_all(connection.cursor(), f"DROP TRIGGER {trigger[0]}")
execute_and_fetch_all(connection.cursor(), "MATCH (n) DETACH DELETE n")

View File

@ -0,0 +1,170 @@
# 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 sys
import mgclient
import pytest
from common import connect, execute_and_fetch_all
@pytest.mark.parametrize("ba_commit", ["BEFORE COMMIT", "AFTER COMMIT"])
def test_create_on_create(ba_commit, connect):
"""
Args:
ba_commit (str): BEFORE OR AFTER commit
"""
cursor = connect.cursor()
QUERY_TRIGGER_CREATE = f"""
CREATE TRIGGER CreateTriggerEdgesCount
ON --> CREATE
{ba_commit}
EXECUTE
CREATE (n:CreatedEdge {{count: size(createdEdges)}})
"""
execute_and_fetch_all(cursor, QUERY_TRIGGER_CREATE)
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(res) == 2
res2 = execute_and_fetch_all(cursor, "MATCH (n:CreatedEdge) RETURN n")
assert len(res2) == 0
QUERY_CREATE_EDGE = """
MATCH (n:Node {id: 1}), (m:Node {id: 2})
CREATE (n)-[r:TYPE]->(m);
"""
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
# See if trigger was triggered
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(nodes) == 2
created_edges = execute_and_fetch_all(cursor, "MATCH (n:CreatedEdge) RETURN n")
assert len(created_edges) == 1
# execute_and_fetch_all(cursor, "DROP TRIGGER CreateTriggerEdgesCount")
# execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
@pytest.mark.parametrize("ba_commit", ["AFTER COMMIT", "BEFORE COMMIT"])
def test_create_on_delete(ba_commit, connect):
"""
Args:
ba_commit (str): BEFORE OR AFTER commit
"""
cursor = connect.cursor()
QUERY_TRIGGER_CREATE = f"""
CREATE TRIGGER DeleteTriggerEdgesCount
ON --> DELETE
{ba_commit}
EXECUTE
CREATE (n:DeletedEdge {{count: size(deletedEdges)}})
"""
# Setup queries
execute_and_fetch_all(cursor, QUERY_TRIGGER_CREATE)
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 3})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 4})")
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(res) == 4
res2 = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
assert len(res2) == 0
# create an edge that will be deleted
QUERY_CREATE_EDGE = """
MATCH (n:Node {id: 1}), (m:Node {id: 2})
CREATE (n)-[r:TYPE]->(m);
"""
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
# create an edge that won't be deleted
QUERY_CREATE_EDGE_NO_DELETE = """
MATCH (n:Node {id: 3}), (m:Node {id: 4})
CREATE (n)-[r:NO_DELETE_EDGE]->(m);
"""
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE_NO_DELETE)
# Delete only one type of the edger
QUERY_DELETE_EDGE = """
MATCH ()-[r:TYPE]->()
DELETE r;
"""
execute_and_fetch_all(cursor, QUERY_DELETE_EDGE)
# See if trigger was triggered
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(nodes) == 4
# Check how many edges got deleted
deleted_edges = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
assert len(deleted_edges) == 1
# execute_and_fetch_all(cursor, "DROP TRIGGER DeleteTriggerEdgesCount")
# execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")``
@pytest.mark.parametrize("ba_commit", ["BEFORE COMMIT", "AFTER COMMIT"])
def test_create_on_delete_explicit_transaction(ba_commit):
"""
Args:
ba_commit (str): BEFORE OR AFTER commit
"""
connection_with_autocommit = mgclient.connect(host="localhost", port=7687)
connection_with_autocommit.autocommit = True
cursor_autocommit = connection_with_autocommit.cursor()
QUERY_TRIGGER_CREATE = f"""
CREATE TRIGGER DeleteTriggerEdgesCountExplicit
ON --> DELETE
{ba_commit}
EXECUTE
CREATE (n:DeletedEdge {{count: size(deletedEdges)}})
"""
# Setup queries
execute_and_fetch_all(cursor_autocommit, QUERY_TRIGGER_CREATE)
# Start explicit transaction on the execution of the first command
connection_without_autocommit = mgclient.connect(host="localhost", port=7687)
connection_without_autocommit.autocommit = False
cursor = connection_without_autocommit.cursor()
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 3})")
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 4})")
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(res) == 4
res2 = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n;")
assert len(res2) == 0
QUERY_CREATE_EDGE = """
MATCH (n:Node {id: 1}), (m:Node {id: 2})
CREATE (n)-[r:TYPE]->(m);
"""
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
# create an edge that won't be deleted
QUERY_CREATE_EDGE_NO_DELETE = """
MATCH (n:Node {id: 3}), (m:Node {id: 4})
CREATE (n)-[r:NO_DELETE_EDGE]->(m);
"""
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE_NO_DELETE)
# Delete only one type of the edger
QUERY_DELETE_EDGE = """
MATCH ()-[r:TYPE]->()
DELETE r;
"""
execute_and_fetch_all(cursor, QUERY_DELETE_EDGE)
connection_without_autocommit.commit() # finish explicit transaction
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
assert len(nodes) == 4
# Check how many edges got deleted
deleted_nodes_edges = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
assert len(deleted_nodes_edges) == 0
# Delete with the original cursor because triggers aren't allowed in multi-transaction environment
execute_and_fetch_all(cursor_autocommit, "DROP TRIGGER DeleteTriggerEdgesCountExplicit")
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")
connection_without_autocommit.commit() # finish explicit transaction
nodes = execute_and_fetch_all(cursor_autocommit, "MATCH (n) RETURN n")
assert len(nodes) == 0
connection_with_autocommit.close()
connection_without_autocommit.close()
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -2,7 +2,14 @@ bolt_port: &bolt_port "7687"
template_cluster: &template_cluster template_cluster: &template_cluster
cluster: cluster:
main: main:
args: ["--bolt-port", *bolt_port, "--log-level=TRACE"] args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--storage-properties-on-edges=True"]
log_file: "triggers-e2e.log"
setup_queries: []
validation_queries: []
storage_properties_edges_false: &storage_properties_edges_false
cluster:
main:
args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--also-log-to-stderr", "--storage-properties-on-edges=False"]
log_file: "triggers-e2e.log" log_file: "triggers-e2e.log"
setup_queries: [] setup_queries: []
validation_queries: [] validation_queries: []
@ -18,7 +25,7 @@ workloads:
args: ["--bolt-port", *bolt_port] args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/" proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster <<: *template_cluster
- name: "ON DELETE Triggers" - name: "ON DELETE Triggers Storage Properties On Edges True"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete" binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete"
args: ["--bolt-port", *bolt_port] args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/" proc: "tests/e2e/triggers/procedures/"
@ -27,5 +34,8 @@ workloads:
binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges" binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges"
args: ["--bolt-port", *bolt_port] args: ["--bolt-port", *bolt_port]
<<: *template_cluster <<: *template_cluster
- name: "ON DELETE Triggers Storage Properties On Edges False" # should be the same as the python file
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/triggers/procedures/"
args: ["triggers/triggers_properties_false.py"]
<<: *storage_properties_edges_false

View File

@ -66,29 +66,13 @@ Feature: Foreach
| 4 | | 4 |
And no side effects And no side effects
Scenario: Foreach shadowing in create
Given an empty graph
And having executed
"""
FOREACH (i IN [1] | FOREACH (j IN [3,4] | CREATE (i {prop: j})));
"""
When executing query:
"""
MATCH (n) RETURN n.prop
"""
Then the result should be:
| n.prop |
| 3 |
| 4 |
And no side effects
Scenario: Foreach set Scenario: Foreach set
Given an empty graph Given an empty graph
And having executed And having executed
""" """
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false }) CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
""" """
And having executed And having executed
""" """
MATCH p=(n1)-[*]->(n2) MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | SET n.marked = true) FOREACH (n IN nodes(p) | SET n.marked = true)
@ -110,7 +94,7 @@ Feature: Foreach
""" """
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false }) CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
""" """
And having executed And having executed
""" """
MATCH p=(n1)-[*]->(n2) MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | REMOVE n.marked) FOREACH (n IN nodes(p) | REMOVE n.marked)
@ -132,7 +116,7 @@ Feature: Foreach
""" """
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false }) CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
""" """
And having executed And having executed
""" """
MATCH p=(n1)-[*]->(n2) MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | DETACH delete n) FOREACH (n IN nodes(p) | DETACH delete n)
@ -148,7 +132,7 @@ Feature: Foreach
Scenario: Foreach merge Scenario: Foreach merge
Given an empty graph Given an empty graph
And having executed And having executed
""" """
FOREACH (i IN [1, 2, 3] | MERGE (n { age : i })) FOREACH (i IN [1, 2, 3] | MERGE (n { age : i }))
""" """
@ -166,7 +150,7 @@ Feature: Foreach
Scenario: Foreach nested Scenario: Foreach nested
Given an empty graph Given an empty graph
And having executed And having executed
""" """
FOREACH (i IN [1, 2, 3] | FOREACH( j IN [1] | CREATE (k { prop : j }))) FOREACH (i IN [1, 2, 3] | FOREACH( j IN [1] | CREATE (k { prop : j })))
""" """
@ -183,11 +167,11 @@ Feature: Foreach
Scenario: Foreach multiple update clauses Scenario: Foreach multiple update clauses
Given an empty graph Given an empty graph
And having executed And having executed
""" """
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false }) CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
""" """
And having executed And having executed
""" """
MATCH p=(n1)-[*]->(n2) MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | SET n.marked1 = true SET n.marked2 = true) FOREACH (n IN nodes(p) | SET n.marked1 = true SET n.marked2 = true)
@ -205,11 +189,11 @@ Feature: Foreach
Scenario: Foreach multiple nested update clauses Scenario: Foreach multiple nested update clauses
Given an empty graph Given an empty graph
And having executed And having executed
""" """
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false }) CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
""" """
And having executed And having executed
""" """
MATCH p=(n1)-[*]->(n2) MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | FOREACH (j IN [1] | SET n.marked1 = true SET n.marked2 = true)) FOREACH (n IN nodes(p) | FOREACH (j IN [1] | SET n.marked1 = true SET n.marked2 = true))
@ -227,7 +211,7 @@ Feature: Foreach
Scenario: Foreach match foreach return Scenario: Foreach match foreach return
Given an empty graph Given an empty graph
And having executed And having executed
""" """
CREATE (n {prop: [[], [1,2]]}); CREATE (n {prop: [[], [1,2]]});
""" """
@ -242,7 +226,7 @@ Feature: Foreach
Scenario: Foreach on null value Scenario: Foreach on null value
Given an empty graph Given an empty graph
And having executed And having executed
""" """
CREATE (n); CREATE (n);
""" """
@ -254,9 +238,9 @@ Feature: Foreach
| | | |
And no side effects And no side effects
Scenario: Foreach nested merge Scenario: Foreach nested merge
Given an empty graph Given an empty graph
And having executed And having executed
""" """
FOREACH(i in [1, 2, 3] | foreach(j in [1] | MERGE (n { age : i }))); FOREACH(i in [1, 2, 3] | foreach(j in [1] | MERGE (n { age : i })));
""" """

View File

@ -15,6 +15,26 @@ Feature: All Shortest Path
| '1' | | '1' |
| '3' | | '3' |
Scenario: Test match allShortest upper bound 2
Given an empty graph
And having executed:
"""
CREATE (a {a:'0'})-[:r {w: 2}]->(b {a:'1'})-[:r {w: 3}]->(c {a:'2'}),
(a)-[:re {w: 2}]->(b),
(b)-[:re {w:3}]->(c),
({a: '4'})<-[:r {w: 1}]-(a),
({a: '5'})<-[:r {w: 1}]-(a),
(c)-[:r {w: 1}]->({a: '6'}),
(c)-[:r {w: 1}]->({a: '7'})
"""
When executing query:
"""
MATCH path=(n {a:'0'})-[r *allShortest ..2 (e, n | 1 ) w]->(m {a:'2'}) RETURN COUNT(path) AS c
"""
Then the result should be:
| c |
| 4 |
Scenario: Test match allShortest filtered Scenario: Test match allShortest filtered
Given an empty graph Given an empty graph
And having executed: And having executed:

View File

@ -45,6 +45,21 @@ class BoltClient : public ::testing::Test {
return true; return true;
} }
bool ExecuteAndCheckQid(const std::string &query, int qid, const std::string &message = "") {
try {
auto ret = client_.Execute(query, {});
if (ret.metadata["qid"].ValueInt() != qid) {
return false;
}
} catch (const ClientQueryException &e) {
if (message != "") {
EXPECT_EQ(e.what(), message);
}
throw;
}
return true;
}
int64_t GetCount() { int64_t GetCount() {
auto ret = client_.Execute("match (n) return count(n)", {}); auto ret = client_.Execute("match (n) return count(n)", {});
EXPECT_EQ(ret.records.size(), 1); EXPECT_EQ(ret.records.size(), 1);
@ -461,6 +476,18 @@ TEST_F(BoltClient, MixedCaseAndWhitespace) {
EXPECT_FALSE(TransactionActive()); EXPECT_FALSE(TransactionActive());
} }
TEST_F(BoltClient, TestQid) {
for (int i = 0; i < 3; ++i) {
EXPECT_TRUE(Execute("match (n) return count(n)"));
}
EXPECT_TRUE(Execute("begin"));
for (int i = 0; i < 3; ++i) {
EXPECT_TRUE(ExecuteAndCheckQid("match (n) return count(n)", i + 1));
}
EXPECT_TRUE(Execute("commit"));
EXPECT_FALSE(TransactionActive());
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
gflags::ParseCommandLineFlags(&argc, &argv, true); gflags::ParseCommandLineFlags(&argc, &argv, 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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -58,7 +58,7 @@ TEST_F(CppApiTestFixture, TestGraph) {
ASSERT_EQ(graph.Order(), 2); ASSERT_EQ(graph.Order(), 2);
ASSERT_EQ(graph.Size(), 0); ASSERT_EQ(graph.Size(), 0);
auto relationship = graph.CreateRelationship(node_1, node_2, "edge_type"); auto relationship_1 = graph.CreateRelationship(node_1, node_2, "edge_type");
ASSERT_EQ(graph.Order(), 2); ASSERT_EQ(graph.Order(), 2);
ASSERT_EQ(graph.Size(), 1); ASSERT_EQ(graph.Size(), 1);
@ -66,7 +66,23 @@ TEST_F(CppApiTestFixture, TestGraph) {
ASSERT_EQ(graph.ContainsNode(node_1), true); ASSERT_EQ(graph.ContainsNode(node_1), true);
ASSERT_EQ(graph.ContainsNode(node_2), true); ASSERT_EQ(graph.ContainsNode(node_2), true);
ASSERT_EQ(graph.ContainsRelationship(relationship), true); ASSERT_EQ(graph.ContainsRelationship(relationship_1), true);
auto node_3 = graph.CreateNode();
auto relationship_2 = graph.CreateRelationship(node_1, node_3, "edge_type");
auto relationship_3 = graph.CreateRelationship(node_2, node_3, "edge_type");
for (const auto &n : graph.Nodes()) {
ASSERT_EQ(graph.ContainsNode(n), true);
}
std::uint64_t n_rels = 0;
for (const auto &r : graph.Relationships()) {
ASSERT_EQ(graph.ContainsRelationship(r), true);
n_rels++;
}
ASSERT_EQ(n_rels, 3);
} }
TEST_F(CppApiTestFixture, TestId) { TEST_F(CppApiTestFixture, TestId) {
@ -195,7 +211,7 @@ TEST_F(CppApiTestFixture, TestNode) {
ASSERT_EQ(node_1.HasLabel("L1"), true); ASSERT_EQ(node_1.HasLabel("L1"), true);
ASSERT_EQ(node_1.HasLabel("L2"), true); ASSERT_EQ(node_1.HasLabel("L2"), true);
ASSERT_EQ(node_1.Properties().Size(), 0); ASSERT_EQ(node_1.Properties().size(), 0);
auto node_2 = graph.GetNodeById(node_1.Id()); auto node_2 = graph.GetNodeById(node_1.Id());
@ -264,7 +280,7 @@ TEST_F(CppApiTestFixture, TestRelationship) {
auto relationship = graph.CreateRelationship(node_1, node_2, "edge_type"); auto relationship = graph.CreateRelationship(node_1, node_2, "edge_type");
ASSERT_EQ(relationship.Type(), "edge_type"); ASSERT_EQ(relationship.Type(), "edge_type");
ASSERT_EQ(relationship.Properties().Size(), 0); ASSERT_EQ(relationship.Properties().size(), 0);
ASSERT_EQ(relationship.From().Id(), node_1.Id()); ASSERT_EQ(relationship.From().Id(), node_1.Id());
ASSERT_EQ(relationship.To().Id(), node_2.Id()); ASSERT_EQ(relationship.To().Id(), node_2.Id());
@ -419,3 +435,19 @@ TEST_F(CppApiTestFixture, TestDuration) {
// Use Value move constructor // Use Value move constructor
auto value_y = mgp::Value(mgp::Duration("PT2M2.33S")); auto value_y = mgp::Value(mgp::Duration("PT2M2.33S"));
} }
TEST_F(CppApiTestFixture, TestNodeProperties) {
mgp_graph raw_graph = CreateGraph(memgraph::storage::View::NEW);
auto graph = mgp::Graph(&raw_graph);
auto node_1 = graph.CreateNode();
ASSERT_EQ(node_1.Properties().size(), 0);
std::map<std::string, mgp::Value> node1_prop = node_1.Properties();
node_1.SetProperty("b", mgp::Value("b"));
ASSERT_EQ(node_1.Properties().size(), 1);
ASSERT_EQ(node_1.Properties()["b"].ValueString(), "b");
ASSERT_EQ(node_1.GetProperty("b").ValueString(), "b");
}

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@ -662,6 +662,30 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchWhereReturn) {
DeleteListContent(&optional); DeleteListContent(&optional);
} }
TYPED_TEST(TestPlanner, MatchOptionalMatchNodePropertyWithIndex) {
// Test MATCH (n:Label) OPTIONAL MATCH (m:Label) WHERE n.prop = m.prop RETURN n
AstStorage storage;
FakeDbAccessor dba;
const auto label_name = "label";
const auto label = dba.Label(label_name);
const auto property = PROPERTY_PAIR("prop");
dba.SetIndexCount(label, property.second, 0);
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n", label_name))), OPTIONAL_MATCH(PATTERN(NODE("m", label_name))),
WHERE(EQ(PROPERTY_LOOKUP("n", property.second), PROPERTY_LOOKUP("m", property.second))), RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
auto m_prop = PROPERTY_LOOKUP("m", property);
std::list<BaseOpChecker *> optional{new ExpectScanAllByLabelPropertyValue(label, property, m_prop)};
CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectFilter(), ExpectOptional(optional), ExpectProduce());
DeleteListContent(&optional);
}
TYPED_TEST(TestPlanner, MatchUnwindReturn) { TYPED_TEST(TestPlanner, MatchUnwindReturn) {
// Test MATCH (n) UNWIND [1,2,3] AS x RETURN n, x // Test MATCH (n) UNWIND [1,2,3] AS x RETURN n, x
AstStorage storage; AstStorage storage;
@ -1685,5 +1709,30 @@ TYPED_TEST(TestPlanner, Foreach) {
QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))}))); QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))})));
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult()); CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
} }
{
// FOREACH with index
// FOREACH (n in [...] | MERGE (v:Label));
const auto label_name = "label";
const auto label = dba.Label(label_name);
dba.SetIndexCount(label, 0);
auto *n = NEXPR("n", IDENT("n"));
auto *query = QUERY(SINGLE_QUERY(FOREACH(n, {MERGE(PATTERN(NODE("v", label_name)))})));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
std::list<BaseOpChecker *> on_match{new ExpectScanAllByLabel()};
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
auto create = ExpectMerge(on_match, on_create);
std::list<BaseOpChecker *> updates{&create};
std::list<BaseOpChecker *> input;
CheckPlan(planner.plan(), symbol_table, ExpectForeach(input, updates), ExpectEmptyResult());
DeleteListContent(&on_match);
DeleteListContent(&on_create);
}
} }
} // namespace } // namespace

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 // 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 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source