Compare commits

...

18 Commits

Author SHA1 Message Date
antoniofilipovic
11edd65757 add fix for property lookup 2023-02-08 18:18:23 +01:00
antoniofilipovic
8c58eddd54 add option for gperftools 2023-02-08 15:40:24 +01:00
antoniofilipovic
13c33149e5 add std::vector instead of std::map 2023-02-08 15:38:48 +01:00
antoniofilipovic
af519d517a implement SetProperties in PropertyStore 2023-02-07 11:20:19 +01:00
antoniofilipovic
49eb6390ff add SetBatchProperties functionality in storage::VertexAccessor 2023-02-06 12:21:22 +01:00
antoniofilipovic
b9ae685c48 introduce std::unordered_map in Parameters to reduce lookup 2023-02-02 13:38:41 +01:00
antoniofilipovic
41622cb765 implement ReferenceExpressionEvaluator with Visitor to Identifier to retrieve pointer to object from frame 2023-02-01 16:21:25 +01:00
antoniofilipovic
a34a2f4140 test reading speed 2023-01-30 14:59:07 +01:00
Bruno Sačarić
19832b5838 Merge pull request #749 from memgraph/update-license-year-2.5.2
[master < MG] Update license year
2023-01-26 13:08:47 +01:00
Bruno Sačarić
e63fae2d5b Update license year 2023-01-26 13:02:33 +01:00
Bruno Sačarić
34dd47ef07 Fix nested FOREACH shadowing bug (#725) 2023-01-25 20:06:05 +01:00
Jure Bajic
d3f275b231 Exclude license dir from CI trigger (#740) 2023-01-25 18:49:02 +01:00
Ante Pušić
aad4bcb7a0 Fix C++ API memory leak on Relationships() (#743) 2023-01-25 17:23:46 +01:00
Bruno Sačarić
034b54cb72 Fix bug on all shortest paths with an upper bound (#737) 2023-01-25 15:32:00 +01:00
Josipmrden
8cf51d9f68 Fix bug in query plan to use indexes on optional match and foreach (#736)
* Add fix in query plan to use indexes on optional match and foreach
2023-01-25 12:53:33 +01:00
Antonio Filipovic
1cd1da84fd Fix bug on (vertex|edge) properties in C++ API (#732) 2023-01-23 12:57:17 +01:00
Jure Bajic
128a6cd522 Update license year (#739) 2023-01-19 12:31:59 +01:00
niko4299
d9eeedb9ee Adding qid in bolt (#721) 2023-01-18 16:33:03 +01:00
28 changed files with 641 additions and 301 deletions

View File

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

View File

@@ -6,7 +6,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 22.8.0
hooks:
- id: black
- 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
// 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);
}
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) {
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);
}
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) {
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
// 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 Value;
struct StealType {};
inline constexpr StealType steal{};
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.
class Id {
@@ -159,7 +162,7 @@ class Nodes {
explicit Iterator(mgp_vertices_iterator *nodes_iterator);
Iterator(const Iterator &other);
Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete;
~Iterator();
@@ -207,7 +210,7 @@ class GraphRelationships {
explicit Iterator(mgp_vertices_iterator *nodes_iterator);
Iterator(const Iterator &other);
Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete;
~Iterator();
@@ -253,7 +256,7 @@ class Relationships {
explicit Iterator(mgp_edges_iterator *relationships_iterator);
Iterator(const Iterator &other);
Iterator(const Iterator &other) noexcept;
Iterator &operator=(const Iterator &other) = delete;
~Iterator();
@@ -281,46 +284,12 @@ class Relationships {
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.
class Labels {
public:
explicit Labels(mgp_vertex *node_ptr);
Labels(const Labels &other);
Labels(const Labels &other) noexcept;
Labels(Labels &&other) noexcept;
Labels &operator=(const Labels &other) noexcept;
@@ -363,6 +332,7 @@ class Labels {
private:
mgp_vertex *node_ptr_;
};
/* #endregion */
/* #region Types */
@@ -397,7 +367,7 @@ class List {
/// @brief Creates a List from the given initializer_list.
explicit List(const std::initializer_list<Value> list);
List(const List &other);
List(const List &other) noexcept;
List(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).
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 &operator=(const Map &other) noexcept;
@@ -519,7 +489,7 @@ class Map {
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();
@@ -578,7 +548,7 @@ class Node {
/// @brief Creates a Node from the copy of the given @ref mgp_vertex.
explicit Node(const mgp_vertex *const_ptr);
Node(const Node &other);
Node(const Node &other) noexcept;
Node(Node &&other) noexcept;
Node &operator=(const Node &other) noexcept;
@@ -590,16 +560,19 @@ class Node {
mgp::Id Id() const;
/// @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`.
bool HasLabel(std::string_view label) const;
/// @brief Returns an iterable & indexable structure of the nodes properties.
class Properties Properties() const;
/// @brief Returns an std::map of the nodes properties.
std::map<std::string, Value> Properties() const;
/// @brief Returns the value of the nodes `property_name` property.
Value operator[](const std::string_view property_name) const;
/// @brief Sets the chosen property to the given value.
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.
Relationships InRelationships() const;
@@ -635,7 +608,7 @@ class Relationship {
/// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
explicit Relationship(const mgp_edge *const_ptr);
Relationship(const Relationship &other);
Relationship(const Relationship &other) noexcept;
Relationship(Relationship &&other) noexcept;
Relationship &operator=(const Relationship &other) noexcept;
@@ -649,11 +622,14 @@ class Relationship {
/// @brief Returns the relationships type.
std::string_view Type() const;
/// @brief Returns an iterable & indexable structure of the relationships properties.
class Properties Properties() const;
/// @brief Returns an std::map of the relationships properties.
std::map<std::string, Value> Properties() const;
/// @brief Returns the value of the relationships `property_name` property.
Value operator[](const std::string_view property_name) const;
/// @brief Sets the chosen property to the given value.
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.
Node From() const;
@@ -688,7 +664,7 @@ class Path {
/// @brief Creates a Path starting with the given `start_node`.
explicit Path(const Node &start_node);
Path(const Path &other);
Path(const Path &other) noexcept;
Path(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.
Date(int year, int month, int day);
Date(const Date &other);
Date(const Date &other) noexcept;
Date(Date &&other) noexcept;
Date &operator=(const Date &other) noexcept;
@@ -799,7 +775,7 @@ class LocalTime {
/// properties.
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 &operator=(const LocalTime &other) noexcept;
@@ -858,7 +834,7 @@ class LocalDateTime {
/// `millisecond`, and `microsecond` properties.
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 &operator=(const LocalDateTime &other) noexcept;
@@ -929,7 +905,7 @@ class Duration {
/// `microsecond` properties.
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 &operator=(const Duration &other) noexcept;
@@ -986,6 +962,8 @@ class Value {
explicit Value(mgp_value *ptr);
explicit Value(StealType /*steal*/, mgp_value *ptr);
// Null constructor:
explicit Value();
@@ -1056,7 +1034,7 @@ class Value {
/// @note The behavior of accessing `duration` after performing this operation is undefined.
explicit Value(Duration &&duration);
Value(const Value &other);
Value(const Value &other) noexcept;
Value(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() {
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() {
if (nodes_iterator_ != nullptr) {
@@ -1814,10 +1792,12 @@ inline GraphRelationships::Iterator &GraphRelationships::Iterator::operator++()
if (out_relationships_iterator_ != nullptr) {
auto next = mgp::edges_iterator_next(out_relationships_iterator_);
if (next == nullptr) {
mgp::edges_iterator_destroy(out_relationships_iterator_);
out_relationships_iterator_ = nullptr;
if (next != nullptr) {
return *this;
}
mgp::edges_iterator_destroy(out_relationships_iterator_);
out_relationships_iterator_ = nullptr;
}
// 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() {
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); }
// 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:
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; }
@@ -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; }
@@ -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; }
@@ -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() {
if (map_items_iterator_ != nullptr) {
@@ -2306,10 +2257,6 @@ inline void Map::Insert(std::string_view key, Value &&value) {
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 !(*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 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; }
@@ -2355,7 +2302,7 @@ inline Node::~Node() {
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 {
for (const auto node_label : Labels()) {
@@ -2366,10 +2313,6 @@ inline bool Node::HasLabel(std::string_view label) const {
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 {
auto relationship_iterator = mgp::vertex_iter_in_edges(ptr_, memory);
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()});
}
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 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)
: 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; }
@@ -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 class Properties Relationship::Properties() const {
return mgp::Properties(mgp::edge_iter_properties(ptr_, memory));
inline std::map<std::string, Value> Relationship::Properties() const {
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 {
return Properties()[property_name];
inline void Relationship::SetProperty(std::string property, Value value) {
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_)); }
@@ -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 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; }
@@ -2533,7 +2508,7 @@ inline Date::Date(int year, int month, int day) {
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; }
@@ -2627,7 +2602,7 @@ inline LocalTime::LocalTime(int hour, int minute, int second, int millisecond, i
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; };
@@ -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);
}
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; };
@@ -2845,7 +2820,7 @@ inline Duration::Duration(double day, double hour, double minute, double second,
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; };
@@ -2917,6 +2892,7 @@ inline bool Duration::operator<(const Duration &other) const {
/* #region Value */
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)) {}
@@ -2997,7 +2973,7 @@ inline Value::Value(Duration &&duration) {
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; }

2
init
View File

@@ -149,7 +149,7 @@ python3 -m pre_commit install
# Install py format tools
echo "Install black formatter"
python3 -m pip install black==22.10.*
python3 -m pip install black==22.8.*
echo "Install isort"
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
which competes (or might reasonably be expected to
compete) with the Licensed Work.
CHANGE DATE: 2026-13-12
CHANGE DATE: 2027-26-01
CHANGE LICENSE: Apache License, Version 2.0
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.

View File

@@ -52,7 +52,7 @@ target_link_libraries(memgraph ${mg_single_node_v2_libs})
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
# should be dynamically exported, so that `dlopen` can correctly link the
# symbols in custom procedure module libraries.
target_link_libraries(memgraph "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/include/mg_procedure.syms")
target_link_libraries(memgraph "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/include/mg_procedure.syms" "-lprofiler")
set_target_properties(memgraph PROPERTIES
# Set the executable output name to include version information.

View File

@@ -146,9 +146,10 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
throw ServerMalformedDataException();
}
auto &header = fields.ValueMap();
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
auto &header = fields.ValueMap();
if (header.find("fields") == header.end()) {
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()));
}
if (header.contains("qid")) {
ret.metadata["qid"] = header["qid"];
}
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
// 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 {
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>
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
try {
@@ -229,7 +195,36 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
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>
@@ -257,7 +252,40 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
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>

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
@@ -125,6 +125,11 @@ class VertexAccessor final {
return impl_.SetProperty(key, value);
}
storage::Result<std::vector<storage::PropertyValue>> SetProperties(
std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties) {
return impl_.SetProperties(properties);
}
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
return SetProperty(key, storage::PropertyValue());
}

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
@@ -64,14 +64,6 @@ auto SymbolGenerator::CreateSymbol(const std::string &name, bool user_declared,
return symbol;
}
auto SymbolGenerator::GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type) {
auto &scope = scopes_.back();
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
return *maybe_symbol;
}
return CreateSymbol(name, user_declared, type);
}
auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) {
// NOLINTNEXTLINE
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
@@ -206,7 +198,7 @@ bool SymbolGenerator::PreVisit(CallProcedure &call_proc) {
bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
for (auto *ident : call_proc.result_identifiers_) {
if (HasSymbolLocalScope(ident->name_)) {
if (HasSymbol(ident->name_)) {
throw RedeclareVariableError(ident->name_);
}
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::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_);
}
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) {
const auto &name = unwind.named_expression_->name_;
if (HasSymbolLocalScope(name)) {
if (HasSymbol(name)) {
throw RedeclareVariableError(name);
}
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
// reference symbols out of bind order.
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_);
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 we are in the pattern, and outside of a node or an edge, the
// identifier is the pattern name.
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH);
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::PATH);
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
// Patterns used to create nodes and edges cannot redeclare already
// established bindings. Declaration only happens in single node
@@ -322,19 +314,19 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
// since `n` now references the bound node instead of declaring it.
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) {
if ((scope.in_create_node || scope.in_create_edge) && HasSymbol(ident.name_)) {
throw RedeclareVariableError(ident.name_);
}
auto type = Symbol::Type::VERTEX;
if (scope.visiting_edge) {
// Edge referencing is not allowed (like in Neo4j):
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
if (HasSymbolLocalScope(ident.name_)) {
if (HasSymbol(ident.name_)) {
throw RedeclareVariableError(ident.name_);
}
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
}
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, type);
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) {
// Prevent variable path bounds to reference the identifier which is bound
@@ -461,7 +453,7 @@ bool SymbolGenerator::PreVisit(NodeAtom &node_atom) {
auto &scope = scopes_.back();
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
const auto &node_name = node_atom.identifier_->name_;
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) {
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbol(node_name)) {
throw SemanticException("Cannot create node '" + node_name +
"' with labels or properties, because it is already declared.");
}
@@ -555,11 +547,11 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
edge_atom.identifier_->Accept(*this);
scope.in_pattern_atom_identifier = false;
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_);
}
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope(
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
edge_atom.total_weight_->MapTo(GetOrCreateSymbol(edge_atom.total_weight_->name_,
edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
}
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); });
}
bool SymbolGenerator::HasSymbolLocalScope(const std::string &name) const {
return scopes_.back().symbols.contains(name);
}
bool SymbolGenerator::ConsumePredefinedIdentifier(const std::string &name) {
auto it = predefined_identifiers_.find(name);

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
@@ -134,7 +134,6 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope, Symbol::Type type);
bool HasSymbol(const std::string &name) const;
bool HasSymbolLocalScope(const std::string &name) const;
// @return true if it added a predefined identifier with that name
bool ConsumePredefinedIdentifier(const std::string &name);
@@ -147,7 +146,6 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
auto GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
// Returns the symbol by name. If the mapping already exists, checks if the
// types match. Otherwise, returns a new symbol.
auto GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
void VisitReturnBody(ReturnBody &body, Where *where = nullptr);

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
@@ -31,6 +31,78 @@
namespace memgraph::query {
static std::chrono::duration<double> total2;
static std::chrono::duration<double> new_print2;
class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> {
public:
ReferenceExpressionEvaluator(Frame *frame, const SymbolTable *symbol_table, const EvaluationContext *ctx,
DbAccessor *dba, storage::View view)
: frame_(frame), symbol_table_(symbol_table), ctx_(ctx), dba_(dba), view_(view) {}
using ExpressionVisitor<TypedValue *>::Visit;
utils::MemoryResource *GetMemoryResource() const { return ctx_->memory; }
#define UNSUCCESFUL_VISIT(expr_name) \
TypedValue *Visit(expr_name &expr) override { return nullptr; }
TypedValue *Visit(Identifier &ident) override { return &frame_->at(symbol_table_->at(ident)); }
UNSUCCESFUL_VISIT(NamedExpression);
UNSUCCESFUL_VISIT(OrOperator);
UNSUCCESFUL_VISIT(XorOperator);
UNSUCCESFUL_VISIT(AdditionOperator);
UNSUCCESFUL_VISIT(SubtractionOperator);
UNSUCCESFUL_VISIT(MultiplicationOperator);
UNSUCCESFUL_VISIT(DivisionOperator);
UNSUCCESFUL_VISIT(ModOperator);
UNSUCCESFUL_VISIT(NotEqualOperator);
UNSUCCESFUL_VISIT(EqualOperator);
UNSUCCESFUL_VISIT(LessOperator);
UNSUCCESFUL_VISIT(GreaterOperator);
UNSUCCESFUL_VISIT(LessEqualOperator);
UNSUCCESFUL_VISIT(GreaterEqualOperator);
UNSUCCESFUL_VISIT(NotOperator);
UNSUCCESFUL_VISIT(UnaryPlusOperator);
UNSUCCESFUL_VISIT(UnaryMinusOperator);
UNSUCCESFUL_VISIT(AndOperator);
UNSUCCESFUL_VISIT(IfOperator);
UNSUCCESFUL_VISIT(InListOperator);
UNSUCCESFUL_VISIT(SubscriptOperator);
UNSUCCESFUL_VISIT(ListSlicingOperator);
UNSUCCESFUL_VISIT(IsNullOperator);
UNSUCCESFUL_VISIT(PropertyLookup);
UNSUCCESFUL_VISIT(LabelsTest);
UNSUCCESFUL_VISIT(PrimitiveLiteral);
UNSUCCESFUL_VISIT(ListLiteral);
UNSUCCESFUL_VISIT(MapLiteral);
UNSUCCESFUL_VISIT(Aggregation);
UNSUCCESFUL_VISIT(Coalesce);
UNSUCCESFUL_VISIT(Function);
UNSUCCESFUL_VISIT(Reduce);
UNSUCCESFUL_VISIT(Extract);
UNSUCCESFUL_VISIT(All);
UNSUCCESFUL_VISIT(Single);
UNSUCCESFUL_VISIT(Any);
UNSUCCESFUL_VISIT(None);
UNSUCCESFUL_VISIT(ParameterLookup);
UNSUCCESFUL_VISIT(RegexMatch);
private:
Frame *frame_;
const SymbolTable *symbol_table_;
const EvaluationContext *ctx_;
DbAccessor *dba_;
// which switching approach should be used when evaluating
storage::View view_;
};
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
public:
ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, DbAccessor *dba,
@@ -159,49 +231,51 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
TypedValue Visit(SubscriptOperator &list_indexing) override {
auto lhs = list_indexing.expression1_->Accept(*this);
ReferenceExpressionEvaluator referenceExpressionEvaluator(frame_, symbol_table_, ctx_, dba_, view_);
auto *lhs = list_indexing.expression1_->Accept(referenceExpressionEvaluator);
auto index = list_indexing.expression2_->Accept(*this);
if (!lhs.IsList() && !lhs.IsMap() && !lhs.IsVertex() && !lhs.IsEdge() && !lhs.IsNull())
if (!lhs->IsList() && !lhs->IsMap() && !lhs->IsVertex() && !lhs->IsEdge() && !lhs->IsNull())
throw QueryRuntimeException(
"Expected a list, a map, a node or an edge to index with '[]', got "
"{}.",
lhs.type());
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
if (lhs.IsList()) {
lhs->type());
if (lhs->IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
if (lhs->IsList()) {
if (!index.IsInt()) throw QueryRuntimeException("Expected an integer as a list index, got {}.", index.type());
auto index_int = index.ValueInt();
// NOTE: Take non-const reference to list, so that we can move out the
// indexed element as the result.
auto &list = lhs.ValueList();
const auto &list = lhs->ValueList();
if (index_int < 0) {
index_int += static_cast<int64_t>(list.size());
}
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0) return TypedValue(ctx_->memory);
// NOTE: Explicit move is needed, so that we return the move constructed
// value and preserve the correct MemoryResource.
return std::move(list[index_int]);
return TypedValue(list[index_int]);
}
if (lhs.IsMap()) {
if (lhs->IsMap()) {
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a map index, got {}.", index.type());
// NOTE: Take non-const reference to map, so that we can move out the
// looked-up element as the result.
auto &map = lhs.ValueMap();
const auto &map = lhs->ValueMap();
auto found = map.find(index.ValueString());
if (found == map.end()) return TypedValue(ctx_->memory);
// NOTE: Explicit move is needed, so that we return the move constructed
// value and preserve the correct MemoryResource.
return std::move(found->second);
return TypedValue(found->second);
}
if (lhs.IsVertex()) {
if (lhs->IsVertex()) {
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
return TypedValue(GetProperty(lhs.ValueVertex(), index.ValueString()), ctx_->memory);
return TypedValue(GetProperty(lhs->ValueVertex(), index.ValueString()), ctx_->memory);
}
if (lhs.IsEdge()) {
if (lhs->IsEdge()) {
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
return TypedValue(GetProperty(lhs.ValueEdge(), index.ValueString()), ctx_->memory);
return TypedValue(GetProperty(lhs->ValueEdge(), index.ValueString()), ctx_->memory);
}
// lhs is Null
@@ -258,7 +332,10 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
TypedValue Visit(PropertyLookup &property_lookup) override {
auto expression_result = property_lookup.expression_->Accept(*this);
ReferenceExpressionEvaluator referenceExpressionEvaluator(frame_, symbol_table_, ctx_, dba_, view_);
auto *expression_result = property_lookup.expression_->Accept(referenceExpressionEvaluator);
// auto expression_result = property_lookup.expression_->Accept(*this);
auto maybe_date = [this](const auto &date, const auto &prop_name) -> std::optional<TypedValue> {
if (prop_name == "year") {
return TypedValue(date.year, ctx_->memory);
@@ -332,42 +409,42 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
return std::nullopt;
};
switch (expression_result.type()) {
switch (expression_result->type()) {
case TypedValue::Type::Null:
return TypedValue(ctx_->memory);
case TypedValue::Type::Vertex:
return TypedValue(GetProperty(expression_result.ValueVertex(), property_lookup.property_), ctx_->memory);
return TypedValue(GetProperty(expression_result->ValueVertex(), property_lookup.property_), ctx_->memory);
case TypedValue::Type::Edge:
return TypedValue(GetProperty(expression_result.ValueEdge(), property_lookup.property_), ctx_->memory);
return TypedValue(GetProperty(expression_result->ValueEdge(), property_lookup.property_), ctx_->memory);
case TypedValue::Type::Map: {
// NOTE: Take non-const reference to map, so that we can move out the
// looked-up element as the result.
auto &map = expression_result.ValueMap();
auto &map = expression_result->ValueMap();
auto found = map.find(property_lookup.property_.name.c_str());
if (found == map.end()) return TypedValue(ctx_->memory);
// NOTE: Explicit move is needed, so that we return the move constructed
// value and preserve the correct MemoryResource.
return std::move(found->second);
return TypedValue(found->second, ctx_->memory);
}
case TypedValue::Type::Duration: {
const auto &prop_name = property_lookup.property_.name;
const auto &dur = expression_result.ValueDuration();
const auto &dur = expression_result->ValueDuration();
if (auto dur_field = maybe_duration(dur, prop_name); dur_field) {
return std::move(*dur_field);
return TypedValue(*dur_field, ctx_->memory);
}
throw QueryRuntimeException("Invalid property name {} for Duration", prop_name);
}
case TypedValue::Type::Date: {
const auto &prop_name = property_lookup.property_.name;
const auto &date = expression_result.ValueDate();
const auto &date = expression_result->ValueDate();
if (auto date_field = maybe_date(date, prop_name); date_field) {
return std::move(*date_field);
return TypedValue(*date_field, ctx_->memory);
}
throw QueryRuntimeException("Invalid property name {} for Date", prop_name);
}
case TypedValue::Type::LocalTime: {
const auto &prop_name = property_lookup.property_.name;
const auto &lt = expression_result.ValueLocalTime();
const auto &lt = expression_result->ValueLocalTime();
if (auto lt_field = maybe_local_time(lt, prop_name); lt_field) {
return std::move(*lt_field);
}
@@ -375,20 +452,20 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
case TypedValue::Type::LocalDateTime: {
const auto &prop_name = property_lookup.property_.name;
const auto &ldt = expression_result.ValueLocalDateTime();
const auto &ldt = expression_result->ValueLocalDateTime();
if (auto date_field = maybe_date(ldt.date, prop_name); date_field) {
return std::move(*date_field);
}
if (auto lt_field = maybe_local_time(ldt.local_time, prop_name); lt_field) {
return std::move(*lt_field);
return TypedValue(*lt_field, ctx_->memory);
}
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
}
case TypedValue::Type::Graph: {
const auto &prop_name = property_lookup.property_.name;
const auto &graph = expression_result.ValueGraph();
const auto &graph = expression_result->ValueGraph();
if (auto graph_field = maybe_graph(graph, prop_name); graph_field) {
return std::move(*graph_field);
return TypedValue(*graph_field, ctx_->memory);
}
throw QueryRuntimeException("Invalid property name {} for Graph", prop_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
@@ -32,7 +32,7 @@ struct Parameters {
* @param position Token position in query of value.
* @param value
*/
void Add(int position, const storage::PropertyValue &value) { storage_.emplace_back(position, value); }
void Add(int position, const storage::PropertyValue &value) { storage_.emplace(position, value); }
/**
* Returns the value found for the given token position.
@@ -41,9 +41,8 @@ struct Parameters {
* @return Value for the given token position.
*/
const storage::PropertyValue &AtTokenPosition(int position) const {
auto found = std::find_if(storage_.begin(), storage_.end(), [&](const auto &a) { return a.first == position; });
MG_ASSERT(found != storage_.end(), "Token position must be present in container");
return found->second;
MG_ASSERT(storage_.contains(position), "Token position must be present in container");
return storage_.at(position);
}
/**
@@ -53,9 +52,9 @@ struct Parameters {
* @param position Which stripped param is sought.
* @return Token position and value for sought param.
*/
const std::pair<int, storage::PropertyValue> &At(int position) const {
const storage::PropertyValue &At(int position) const {
MG_ASSERT(position < static_cast<int>(storage_.size()), "Invalid position");
return storage_[position];
return storage_.at(position);
}
/** Returns the number of arguments in this container */
@@ -65,7 +64,7 @@ struct Parameters {
auto end() const { return storage_.end(); }
private:
std::vector<std::pair<int, storage::PropertyValue>> storage_;
std::unordered_map<int, storage::PropertyValue> storage_;
};
} // namespace memgraph::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
@@ -155,6 +155,9 @@ uint64_t ComputeProfilingKey(const T *obj) {
#define SCOPED_PROFILE_OP(name) ScopedProfile profile{ComputeProfilingKey(this), name, &context};
std::chrono::duration<double> total;
std::chrono::duration<double> new_print;
bool Once::OnceCursor::Pull(Frame &, ExecutionContext &context) {
SCOPED_PROFILE_OP("Once");
@@ -208,10 +211,14 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
storage::View::NEW);
// TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory
// when we update PropertyValue with custom allocator.
std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> properties;
if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) {
for (const auto &[key, value_expression] : *node_info_properties) {
PropsSetChecked(&new_node, key, value_expression->Accept(evaluator));
properties.emplace_back(key, value_expression->Accept(evaluator));
// PropsSetChecked(&new_node, key, );
}
new_node.SetProperties(properties);
} else {
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties));
for (const auto &[key, value] : property_map.ValueMap()) {
@@ -2020,7 +2027,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
next_edges_.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);
frame[self_.common_.edge_symbol] = TypedValue::TVector(memory);
}
@@ -2029,33 +2036,28 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
while (!pq_.empty()) {
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();
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.
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);
}
// if current vertex is not starting vertex, maybe_directed_edge will not be nullopt
if (maybe_directed_edge) {
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();
// Searching for a previous vertex in the expansion
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
// Update the parent
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
utils::pmr::list<DirectedEdge> empty(memory);
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
}
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(*maybe_directed_edge);
// Update the parent
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
utils::pmr::list<DirectedEdge> empty(memory);
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
}
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(directed_edge);
}
if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) {
@@ -2112,8 +2114,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Priority queue comparator. Keep lowest weight on top of the queue.
class PriorityQueueComparator {
public:
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &lhs,
const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &rhs) {
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &lhs,
const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &rhs) {
const auto &lhs_weight = std::get<0>(lhs);
const auto &rhs_weight = std::get<0>(rhs);
// Null defines minimum value for all types
@@ -2132,8 +2134,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Priority queue - core element of the algorithm.
// Stores: {weight, depth, next vertex, edge and direction}
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>,
utils::pmr::vector<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, DirectedEdge>>,
PriorityQueueComparator>
pq_;
@@ -4584,6 +4586,7 @@ class LoadCsvCursor : public Cursor {
// have to read at most cardinality(n) rows (but we can read less and stop
// pulling MATCH).
if (!input_is_once_ && !input_pulled) return false;
auto row = reader_->GetNextRow(context.evaluation_context.memory);
if (!row) {
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
// 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 {
prev_ops_.push_back(&op);
op.input()->Accept(*this);
RewriteBranch(&op.update_clauses_);
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
// 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));
for (const auto &matching : query_part.optional_matching) {
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) {
input_op = std::make_unique<Optional>(std::move(input_op), std::move(match_op), opt_ctx.new_symbols);
}

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
@@ -26,6 +26,9 @@
namespace memgraph::query {
std::chrono::duration<double> total;
std::chrono::duration<double> new_print;
TypedValue::TypedValue(const storage::PropertyValue &value)
// TODO: MemoryResource in storage::PropertyValue
: TypedValue(value, utils::NewDeleteResource()) {}
@@ -289,8 +292,18 @@ TypedValue::operator storage::PropertyValue() const {
return storage::PropertyValue(int_v);
case TypedValue::Type::Double:
return storage::PropertyValue(double_v);
case TypedValue::Type::String:
return storage::PropertyValue(std::string(string_v));
case TypedValue::Type::String: {
auto start = std::chrono::steady_clock::now();
auto value = storage::PropertyValue(std::string(string_v));
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> dif = end - start;
total += dif;
if (total > new_print) {
std::cout << "Time typed value difference = " << total.count() << "[s]" << std::endl;
new_print += static_cast<std::chrono::duration<double>>(10.0);
}
return value;
}
case TypedValue::Type::List:
return storage::PropertyValue(std::vector<storage::PropertyValue>(list_v.begin(), list_v.end()));
case TypedValue::Type::Map: {

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
@@ -869,6 +869,26 @@ SpecificPropertyAndBufferInfo FindSpecificPropertyAndBufferInfo(Reader *reader,
return {property_begin, property_end, property_end - property_begin, all_begin, all_end, all_end - all_begin};
}
// Function used to move to the position where the property should be in the data
// buffer.
// The `property_size` will be `0`
// and `property_begin` will be equal to `property_end`. Positions and size of
// all properties is always calculated (even if the specific property isn't
// found).
//
// @sa FindSpecificProperty
SpecificPropertyAndBufferInfo SkipReaderAndGetBufferInfo(Reader *reader, uint64_t size) {
uint64_t property_begin = reader->GetPosition();
uint64_t property_end = reader->GetPosition();
uint64_t all_begin = reader->GetPosition();
uint64_t all_end = reader->GetPosition();
reader->SkipBytes(size);
property_begin = reader->GetPosition();
property_end = reader->GetPosition();
all_end = reader->GetPosition();
return {property_begin, property_end, property_end - property_begin, all_begin, all_end, all_end - all_begin};
}
// All data buffers will be allocated to a power of 8 size.
uint64_t ToPowerOf8(uint64_t size) {
uint64_t mod = size % 8;
@@ -971,6 +991,8 @@ PropertyValue PropertyStore::GetProperty(PropertyId property) const {
return value;
}
PropertyValue PropertyStore::GetEmptyProperty() const { return PropertyValue(); }
bool PropertyStore::HasProperty(PropertyId property) const {
uint64_t size;
const uint8_t *data;
@@ -1144,6 +1166,57 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value)
return !existed;
}
// use this function only when inserting new properties
bool PropertyStore::SetProperties(std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties) {
uint64_t size;
uint8_t *data;
std::tie(size, data) = GetSizeData(buffer_);
MG_ASSERT(size == 0, "Invalid database state!");
uint64_t property_size = 0;
{
Writer writer;
for (const auto &[property, value] : properties) {
EncodeProperty(&writer, property, value);
property_size = writer.Written();
}
}
auto property_size_to_power_of_8 = ToPowerOf8(property_size);
if (property_size <= sizeof(buffer_) - 1) {
// Use the local buffer.
buffer_[0] = kUseLocalBuffer;
size = sizeof(buffer_) - 1;
data = &buffer_[1];
} else {
// Allocate a new external buffer.
auto alloc_data = new uint8_t[property_size_to_power_of_8];
auto alloc_size = property_size_to_power_of_8;
SetSizeData(buffer_, alloc_size, alloc_data);
size = alloc_size;
data = alloc_data;
}
// Encode the property into the data buffer.
Writer writer(data, size);
for (const auto &[property, value] : properties) {
MG_ASSERT(EncodeProperty(&writer, property, value), "Invalid database state!");
property_size = writer.Written();
}
auto metadata = writer.WriteMetadata();
if (metadata) {
// If there is any space left in the buffer we add a tombstone to
// indicate that there are no more properties to be decoded.
metadata->Set({Type::EMPTY});
}
return true;
}
bool PropertyStore::ClearProperties() {
bool in_local_buffer = false;
uint64_t size;

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
@@ -42,6 +42,8 @@ class PropertyStore {
/// complexity of this function is O(n).
bool HasProperty(PropertyId property) const;
PropertyValue GetEmptyProperty() const;
/// Checks whether the property `property` is equal to the specified value
/// `value`. This function doesn't perform any memory allocations while
/// performing the equality check. The time complexity of this function is
@@ -59,6 +61,8 @@ class PropertyStore {
/// @throw std::bad_alloc
bool SetProperty(PropertyId property, const PropertyValue &value);
bool SetProperties(std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties);
/// Remove all properties and return `true` if any removal took place.
/// `false` is returned if there were no properties to remove. The time
/// complexity of this function is O(1).

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
@@ -13,6 +13,7 @@
#include <memory>
#include <chrono>
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices.hpp"
@@ -60,6 +61,9 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v
} // namespace
} // namespace detail
std::chrono::duration<double> total;
std::chrono::duration<double> new_print;
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
Constraints *constraints, Config::Items config, View view) {
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
@@ -208,6 +212,8 @@ Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
}
Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
// auto start = std::chrono::steady_clock::now();
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
@@ -227,9 +233,44 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
// auto end = std::chrono::steady_clock::now();
// std::chrono::duration<double> dif = end - start;
// total += dif;
// if (total > new_print) {
// std::cout << "Time difference = " << total.count() << "[s]" << std::endl;
// new_print += static_cast<std::chrono::duration<double>>(2.0);
// }
return std::move(current_value);
}
Result<std::vector<storage::PropertyValue>> VertexAccessor::SetProperties(
std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties) {
// Be careful when calling this function
// It will set properties in batch, without checking if property already exists
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
if (vertex_->deleted) return Error::DELETED_OBJECT;
std::vector<storage::PropertyValue> new_values;
vertex_->properties.SetProperties(properties);
for (const auto &[property, value] : properties) {
auto current_value = vertex_->properties.GetEmptyProperty();
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
new_values.emplace_back(current_value);
}
return new_values;
}
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
std::lock_guard<utils::SpinLock> guard(vertex_->lock);

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
@@ -68,6 +68,9 @@ class VertexAccessor final {
/// @throw std::bad_alloc
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
Result<std::vector<storage::PropertyValue>> SetProperties(
std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties);
/// Remove all properties and return the values of the removed properties.
/// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> ClearProperties();

View File

@@ -66,29 +66,13 @@ Feature: Foreach
| 4 |
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
Given an empty graph
And having executed
"""
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
"""
And having executed
And having executed
"""
MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | SET n.marked = true)
@@ -110,7 +94,7 @@ Feature: Foreach
"""
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
"""
And having executed
And having executed
"""
MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | REMOVE n.marked)
@@ -132,7 +116,7 @@ Feature: Foreach
"""
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
"""
And having executed
And having executed
"""
MATCH p=(n1)-[*]->(n2)
FOREACH (n IN nodes(p) | DETACH delete n)
@@ -148,7 +132,7 @@ Feature: Foreach
Scenario: Foreach merge
Given an empty graph
And having executed
And having executed
"""
FOREACH (i IN [1, 2, 3] | MERGE (n { age : i }))
"""
@@ -166,7 +150,7 @@ Feature: Foreach
Scenario: Foreach nested
Given an empty graph
And having executed
And having executed
"""
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
Given an empty graph
And having executed
And having executed
"""
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
"""
And having executed
And having executed
"""
MATCH p=(n1)-[*]->(n2)
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
Given an empty graph
And having executed
And having executed
"""
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
"""
And having executed
And having executed
"""
MATCH p=(n1)-[*]->(n2)
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
Given an empty graph
And having executed
And having executed
"""
CREATE (n {prop: [[], [1,2]]});
"""
@@ -242,7 +226,7 @@ Feature: Foreach
Scenario: Foreach on null value
Given an empty graph
And having executed
And having executed
"""
CREATE (n);
"""
@@ -254,9 +238,9 @@ Feature: Foreach
| |
And no side effects
Scenario: Foreach nested merge
Scenario: Foreach nested merge
Given an empty graph
And having executed
And having executed
"""
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' |
| '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
Given an empty graph
And having executed:

View File

@@ -45,6 +45,21 @@ class BoltClient : public ::testing::Test {
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() {
auto ret = client_.Execute("match (n) return count(n)", {});
EXPECT_EQ(ret.records.size(), 1);
@@ -461,6 +476,18 @@ TEST_F(BoltClient, MixedCaseAndWhitespace) {
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) {
::testing::InitGoogleTest(&argc, argv);
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
// 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.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.Size(), 1);
@@ -66,7 +66,23 @@ TEST_F(CppApiTestFixture, TestGraph) {
ASSERT_EQ(graph.ContainsNode(node_1), 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) {
@@ -195,7 +211,7 @@ TEST_F(CppApiTestFixture, TestNode) {
ASSERT_EQ(node_1.HasLabel("L1"), 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());
@@ -264,7 +280,7 @@ TEST_F(CppApiTestFixture, TestRelationship) {
auto relationship = graph.CreateRelationship(node_1, node_2, "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.To().Id(), node_2.Id());
@@ -419,3 +435,19 @@ TEST_F(CppApiTestFixture, TestDuration) {
// Use Value move constructor
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
// 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);
}
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) {
// Test MATCH (n) UNWIND [1,2,3] AS x RETURN n, x
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")))})));
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

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