From d516e40841562afbf82883e6d7cbbb465926ffe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20Pintari=C4=87?= <99442742+mpintaric55334@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:30:23 +0200 Subject: [PATCH] Add ToString on C++ API mgp types(#1140) --- include/mgp.hpp | 178 +++++++++++++++++++++++++++++++++++++++++ tests/unit/cpp_api.cpp | 74 +++++++++++++++++ 2 files changed, 252 insertions(+) diff --git a/include/mgp.hpp b/include/mgp.hpp index 9085f6350..a56ac23cb 100644 --- a/include/mgp.hpp +++ b/include/mgp.hpp @@ -554,6 +554,9 @@ class List { /// @exception std::runtime_error List contains value of unknown type. bool operator!=(const List &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_list *ptr_; }; @@ -669,6 +672,9 @@ class Map { /// @exception std::runtime_error Map contains value of unknown type. bool operator!=(const Map &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_map *ptr_; }; @@ -740,6 +746,9 @@ class Node { /// @exception std::runtime_error Node properties contain value(s) of unknown type. bool operator!=(const Node &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_vertex *ptr_; }; @@ -798,6 +807,9 @@ class Relationship { /// @exception std::runtime_error Relationship properties contain value(s) of unknown type. bool operator!=(const Relationship &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_edge *ptr_; }; @@ -846,6 +858,9 @@ class Path { /// @exception std::runtime_error Path contains element(s) with unknown value. bool operator!=(const Path &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_path *ptr_; }; @@ -903,6 +918,9 @@ class Date { bool operator<(const Date &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_date *ptr_; }; @@ -962,6 +980,9 @@ class LocalTime { bool operator<(const LocalTime &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_local_time *ptr_; }; @@ -1027,6 +1048,9 @@ class LocalDateTime { bool operator<(const LocalDateTime &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_local_date_time *ptr_; }; @@ -1078,6 +1102,9 @@ class Duration { bool operator<(const Duration &other) const; + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_duration *ptr_; }; @@ -1288,6 +1315,9 @@ class Value { friend std::ostream &operator<<(std::ostream &os, const mgp::Value &value); + /// @brief returns the string representation + const std::string ToString() const; + private: mgp_value *ptr_; }; @@ -2400,6 +2430,22 @@ inline bool List::operator==(const List &other) const { return util::ListsEqual( inline bool List::operator!=(const List &other) const { return !(*this == other); } +inline const std::string List::ToString() const { + const size_t size = Size(); + if (size == 0) { + return "[]"; + } + std::string return_str{"["}; + size_t i = 0; + const mgp::List &list = (*this); + while (i < size - 1) { + return_str.append(list[i].ToString() + ", "); + i++; + } + return_str.append(list[i].ToString() + "]"); + return return_str; +} + // MapItem: inline bool MapItem::operator==(MapItem &other) const { return key == other.key && value == other.value; } @@ -2569,6 +2615,24 @@ inline bool Map::operator==(const Map &other) const { return util::MapsEqual(ptr inline bool Map::operator!=(const Map &other) const { return !(*this == other); } +inline const std::string Map::ToString() const { + const size_t map_size = Size(); + if (map_size == 0) { + return "{}"; + } + std::string return_string{"{"}; + size_t i = 0; + for (const auto &[key, value] : *this) { + if (i == map_size - 1) { + return_string.append(std::string(key) + ": " + value.ToString() + "}"); + break; + } + return_string.append(std::string(key) + ": " + value.ToString() + ", "); + ++i; + } + return return_string; +} + /* #endregion */ /* #region Graph elements (Node, Relationship & Path) */ @@ -2674,6 +2738,35 @@ inline bool Node::operator==(const Node &other) const { return util::NodesEqual( inline bool Node::operator!=(const Node &other) const { return !(*this == other); } +// this functions is used both in relationship and node ToString +inline std::string PropertiesToString(const std::map &property_map) { + std::string properties{""}; + const auto map_size = property_map.size(); + size_t i = 0; + for (const auto &[key, value] : property_map) { + if (i == map_size - 1) { + properties.append(std::string(key) + ": " + value.ToString()); + break; + } + properties.append(std::string(key) + ": " + value.ToString() + ", "); + ++i; + } + return properties; +} + +inline const std::string Node::ToString() const { + std::string labels{", "}; + for (auto label : Labels()) { + labels.append(":" + std::string(label)); + } + if (labels == ", ") { + labels = ""; // dont use labels if they dont exist + } + std::map properties_map{Properties()}; + std::string properties{PropertiesToString(properties_map)}; + return "(id: " + std::to_string(Id().AsInt()) + labels + ", properties: {" + properties + "})"; +} + // Relationship: inline Relationship::Relationship(mgp_edge *ptr) : ptr_(mgp::MemHandlerCallback(edge_copy, ptr)) {} @@ -2748,6 +2841,18 @@ inline bool Relationship::operator==(const Relationship &other) const { inline bool Relationship::operator!=(const Relationship &other) const { return !(*this == other); } +inline const std::string Relationship::ToString() const { + const auto from = From(); + const auto to = To(); + + const std::string type{Type()}; + std::map properties_map{Properties()}; + std::string properties{PropertiesToString(properties_map)}; + const std::string relationship{"[type: " + type + ", id: " + std::to_string(Id().AsInt()) + ", properties: {" + + properties + "}]"}; + + return from.ToString() + "-" + relationship + "->" + to.ToString(); +} // Path: inline Path::Path(mgp_path *ptr) : ptr_(mgp::MemHandlerCallback(path_copy, ptr)) {} @@ -2810,6 +2915,26 @@ inline bool Path::operator==(const Path &other) const { return util::PathsEqual( inline bool Path::operator!=(const Path &other) const { return !(*this == other); } +inline const std::string Path::ToString() const { + const auto length = Length(); + size_t i = 0; + std::string return_string{""}; + for (i = 0; i < length; i++) { + const auto node = GetNodeAt(i); + return_string.append(node.ToString() + "-"); + + const Relationship rel = GetRelationshipAt(i); + std::map properties_map{rel.Properties()}; + std::string properties = PropertiesToString(properties_map); + return_string.append("[type: " + std::string(rel.Type()) + ", id: " + std::to_string(rel.Id().AsInt()) + + ", properties: {" + properties + "}]->"); + } + + const auto node = GetNodeAt(i); + return_string.append(node.ToString()); + return return_string; +} + /* #endregion */ /* #region Temporal types (Date, LocalTime, LocalDateTime, Duration) */ @@ -2907,6 +3032,10 @@ inline bool Date::operator<(const Date &other) const { return is_less; } +inline const std::string Date::ToString() const { + return std::to_string(Year()) + "-" + std::to_string(Month()) + "-" + std::to_string(Day()); +} + // LocalTime: inline LocalTime::LocalTime(mgp_local_time *ptr) : ptr_(mgp::MemHandlerCallback(local_time_copy, ptr)) {} @@ -3006,6 +3135,11 @@ inline bool LocalTime::operator<(const LocalTime &other) const { return is_less; } +inline const std::string LocalTime::ToString() const { + return std::to_string(Hour()) + ":" + std::to_string(Minute()) + ":" + std::to_string(Second()) + "," + + std::to_string(Millisecond()) + std::to_string(Microsecond()); +} + // LocalDateTime: inline LocalDateTime::LocalDateTime(mgp_local_date_time *ptr) @@ -3120,6 +3254,12 @@ inline bool LocalDateTime::operator<(const LocalDateTime &other) const { return is_less; } +inline const std::string LocalDateTime::ToString() const { + return std::to_string(Year()) + "-" + std::to_string(Month()) + "-" + std::to_string(Day()) + "T" + + std::to_string(Hour()) + ":" + std::to_string(Minute()) + ":" + std::to_string(Second()) + "," + + std::to_string(Millisecond()) + std::to_string(Microsecond()); +} + // Duration: inline Duration::Duration(mgp_duration *ptr) : ptr_(mgp::MemHandlerCallback(duration_copy, ptr)) {} @@ -3209,6 +3349,8 @@ inline bool Duration::operator<(const Duration &other) const { return is_less; } +inline const std::string Duration::ToString() const { return std::to_string(Microseconds()) + "ms"; } + /* #endregion */ /* #endregion */ @@ -3673,6 +3815,42 @@ inline std::ostream &operator<<(std::ostream &os, const mgp::Type &type) { } } +inline const std::string Value::ToString() const { + const mgp::Type &type = Type(); + switch (type) { + case Type::Null: + return ""; + case Type::Bool: + return ValueBool() ? "true" : "false"; + case Type::Int: + return std::to_string(ValueInt()); + case Type::Double: + return std::to_string(ValueDouble()); + case Type::String: + return std::string(ValueString()); + case Type::Node: + return ValueNode().ToString(); + case Type::Relationship: + return ValueRelationship().ToString(); + case Type::Date: + return ValueDate().ToString(); + case Type::LocalTime: + return ValueLocalTime().ToString(); + case Type::LocalDateTime: + return ValueLocalDateTime().ToString(); + case Type::Duration: + return ValueDuration().ToString(); + case Type::List: + return ValueList().ToString(); + case Type::Map: + return ValueMap().ToString(); + case Type::Path: + return ValuePath().ToString(); + default: + throw ValueException("Undefined behaviour"); + } +} + /* #endregion */ /* #region Record */ diff --git a/tests/unit/cpp_api.cpp b/tests/unit/cpp_api.cpp index 70e62110e..19852478e 100644 --- a/tests/unit/cpp_api.cpp +++ b/tests/unit/cpp_api.cpp @@ -615,3 +615,77 @@ TYPED_TEST(CppApiTestFixture, TestValuePrint) { std::string date_test = oss_date.str(); ASSERT_EQ("2020-12-12", date_test); } + +TYPED_TEST(CppApiTestFixture, TestValueToString) { + /*graph and node shared by multiple types*/ + mgp_graph raw_graph = this->CreateGraph(memgraph::storage::View::NEW); + auto graph = mgp::Graph(&raw_graph); + + /*null*/ + ASSERT_EQ(mgp::Value().ToString(), ""); + /*bool*/ + ASSERT_EQ(mgp::Value(false).ToString(), "false"); + /*int*/ + const int64_t int1 = 60; + ASSERT_EQ(mgp::Value(int1).ToString(), "60"); + /*double*/ + const double double1 = 2.567891; + ASSERT_EQ(mgp::Value(double1).ToString(), "2.567891"); + /*string*/ + const std::string str = "string"; + ASSERT_EQ(mgp::Value(str).ToString(), "string"); + /*list*/ + mgp::List list; + auto node_list = graph.CreateNode(); + node_list.AddLabel("Label_list"); + list.AppendExtend(mgp::Value("inside")); + list.AppendExtend(mgp::Value("2")); + list.AppendExtend(mgp::Value(node_list)); + ASSERT_EQ(mgp::Value(list).ToString(), "[inside, 2, (id: 0, :Label_list, properties: {})]"); + /*map*/ + mgp::Map map; + auto node_map = graph.CreateNode(); + node_map.AddLabel("Label_map"); + map.Insert("key", mgp::Value(int1)); + map.Insert("node", mgp::Value(node_map)); + ASSERT_EQ(mgp::Value(map).ToString(), "{key: 60, node: (id: 1, :Label_map, properties: {})}"); + /*date*/ + mgp::Date date_1{"2020-12-12"}; + ASSERT_EQ(mgp::Value(date_1).ToString(), "2020-12-12"); + + /*local time*/ + mgp::LocalTime local_time{"09:15:00.360"}; + ASSERT_EQ(mgp::Value(local_time).ToString(), "9:15:0,3600"); + + /*local date time*/ + mgp::LocalDateTime local_date_time{"2021-10-05T14:15:00"}; + ASSERT_EQ(mgp::Value(local_date_time).ToString(), "2021-10-5T14:15:0,00"); + + /*duration*/ + mgp::Duration duration{"P14DT17H2M45S"}; + ASSERT_EQ(mgp::Value(duration).ToString(), "1270965000000ms"); + + /*node and relationship*/ + auto node1 = graph.CreateNode(); + node1.AddLabel("Label1"); + node1.AddLabel("Label2"); + auto node2 = graph.CreateNode(); + node2.SetProperty("key", mgp::Value("node_property")); + node2.SetProperty("key2", mgp::Value("node_property2")); + auto rel = graph.CreateRelationship(node1, node2, "Loves"); + + rel.SetProperty("key", mgp::Value("property")); + ASSERT_EQ(mgp::Value(rel).ToString(), + "(id: 2, :Label1:Label2, properties: {})-[type: Loves, id: 0, properties: {key: property}]->(id: 3, " + "properties: {key: node_property, key2: node_property2})"); + /*path*/ + mgp::Path path = mgp::Path(node1); + path.Expand(rel); + auto node3 = graph.CreateNode(); + auto rel2 = graph.CreateRelationship(node2, node3, "Loves2"); + path.Expand(rel2); + ASSERT_EQ( + mgp::Value(path).ToString(), + "(id: 2, :Label1:Label2, properties: {})-[type: Loves, id: 0, properties: {key: property}]->(id: 3, properties: " + "{key: node_property, key2: node_property2})-[type: Loves2, id: 1, properties: {}]->(id: 4, properties: {})"); +}