From 04ceb8d4b17f26c3e3d29d25f711a1760bfb7721 Mon Sep 17 00:00:00 2001
From: Matej Ferencevic <matej.ferencevic@memgraph.io>
Date: Sat, 5 Sep 2020 13:05:30 +0200
Subject: [PATCH] Implement `valueType` openCypher function

Reviewers: buda

Reviewed By: buda

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2818
---
 .../interpret/awesome_memgraph_functions.cpp  | 40 ++++++++++++++++++-
 tests/unit/query_expression_evaluator.cpp     | 32 +++++++++++++++
 2 files changed, 71 insertions(+), 1 deletion(-)

diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp
index b84c78e61..f4d525565 100644
--- a/src/query/interpret/awesome_memgraph_functions.cpp
+++ b/src/query/interpret/awesome_memgraph_functions.cpp
@@ -74,6 +74,7 @@ struct Integer {};
 struct PositiveInteger {};
 struct NonZeroInteger {};
 struct NonNegativeInteger {};
+struct Double {};
 struct Number {};
 struct List {};
 struct String {};
@@ -96,6 +97,8 @@ bool ArgIsType(const TypedValue &arg) {
     return arg.IsInt() && arg.ValueInt() != 0;
   } else if constexpr (std::is_same_v<ArgType, NonNegativeInteger>) {
     return arg.IsInt() && arg.ValueInt() >= 0;
+  } else if constexpr (std::is_same_v<ArgType, Double>) {
+    return arg.IsDouble();
   } else if constexpr (std::is_same_v<ArgType, Number>) {
     return arg.IsNumeric();
   } else if constexpr (std::is_same_v<ArgType, List>) {
@@ -120,6 +123,8 @@ bool ArgIsType(const TypedValue &arg) {
 
 template <class ArgType>
 constexpr const char *ArgTypeName() {
+  // The type names returned should be standardized openCypher type names.
+  // https://github.com/opencypher/openCypher/blob/master/docs/openCypher9.pdf
   if constexpr (std::is_same_v<ArgType, Null>) {
     return "null";
   } else if constexpr (std::is_same_v<ArgType, Bool>) {
@@ -132,6 +137,8 @@ constexpr const char *ArgTypeName() {
     return "non-zero integer";
   } else if constexpr (std::is_same_v<ArgType, NonNegativeInteger>) {
     return "non-negative integer";
+  } else if constexpr (std::is_same_v<ArgType, Double>) {
+    return "float";
   } else if constexpr (std::is_same_v<ArgType, Number>) {
     return "number";
   } else if constexpr (std::is_same_v<ArgType, List>) {
@@ -143,7 +150,7 @@ constexpr const char *ArgTypeName() {
   } else if constexpr (std::is_same_v<ArgType, Vertex>) {
     return "node";
   } else if constexpr (std::is_same_v<ArgType, Edge>) {
-    return "edge";
+    return "relationship";
   } else if constexpr (std::is_same_v<ArgType, Path>) {
     return "path";
   } else if constexpr (std::is_same_v<ArgType, void>) {
@@ -548,6 +555,36 @@ TypedValue Type(const TypedValue *args, int64_t nargs,
                     ctx.memory);
 }
 
+TypedValue ValueType(const TypedValue *args, int64_t nargs,
+                     const FunctionContext &ctx) {
+  FType<Or<Null, Bool, Integer, Double, String, List, Map, Vertex, Edge, Path>>(
+      "type", args, nargs);
+  // The type names returned should be standardized openCypher type names.
+  // https://github.com/opencypher/openCypher/blob/master/docs/openCypher9.pdf
+  switch (args[0].type()) {
+    case TypedValue::Type::Null:
+      return TypedValue("NULL", ctx.memory);
+    case TypedValue::Type::Bool:
+      return TypedValue("BOOLEAN", ctx.memory);
+    case TypedValue::Type::Int:
+      return TypedValue("INTEGER", ctx.memory);
+    case TypedValue::Type::Double:
+      return TypedValue("FLOAT", ctx.memory);
+    case TypedValue::Type::String:
+      return TypedValue("STRING", ctx.memory);
+    case TypedValue::Type::List:
+      return TypedValue("LIST", ctx.memory);
+    case TypedValue::Type::Map:
+      return TypedValue("MAP", ctx.memory);
+    case TypedValue::Type::Vertex:
+      return TypedValue("NODE", ctx.memory);
+    case TypedValue::Type::Edge:
+      return TypedValue("RELATIONSHIP", ctx.memory);
+    case TypedValue::Type::Path:
+      return TypedValue("PATH", ctx.memory);
+  }
+}
+
 // TODO: How is Keys different from Properties function?
 TypedValue Keys(const TypedValue *args, int64_t nargs,
                 const FunctionContext &ctx) {
@@ -1088,6 +1125,7 @@ NameToFunction(const std::string &function_name) {
   if (function_name == "TOFLOAT") return ToFloat;
   if (function_name == "TOINTEGER") return ToInteger;
   if (function_name == "TYPE") return Type;
+  if (function_name == "VALUETYPE") return ValueType;
 
   // List functions
   if (function_name == "KEYS") return Keys;
diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp
index 29e291412..bb8d1739b 100644
--- a/tests/unit/query_expression_evaluator.cpp
+++ b/tests/unit/query_expression_evaluator.cpp
@@ -1418,6 +1418,38 @@ TEST_F(FunctionTest, Type) {
   ASSERT_THROW(EvaluateFunction("TYPE", 2), QueryRuntimeException);
 }
 
+TEST_F(FunctionTest, ValueType) {
+  ASSERT_THROW(EvaluateFunction("VALUETYPE"), QueryRuntimeException);
+  ASSERT_THROW(EvaluateFunction("VALUETYPE", TypedValue(), TypedValue()),
+               QueryRuntimeException);
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue()).ValueString(), "NULL");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(true)).ValueString(),
+            "BOOLEAN");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1)).ValueString(),
+            "INTEGER");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(1.1)).ValueString(),
+            "FLOAT");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue("test")).ValueString(),
+            "STRING");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", TypedValue(std::vector<TypedValue>{
+                                              TypedValue(1), TypedValue(2)}))
+                .ValueString(),
+            "LIST");
+  ASSERT_EQ(EvaluateFunction("VALUETYPE",
+                             TypedValue(std::map<std::string, TypedValue>{
+                                 {"test", TypedValue(1)}}))
+                .ValueString(),
+            "MAP");
+  auto v1 = dba.InsertVertex();
+  auto v2 = dba.InsertVertex();
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", v1).ValueString(), "NODE");
+  auto e = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("type1"));
+  ASSERT_TRUE(e.HasValue());
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", *e).ValueString(), "RELATIONSHIP");
+  Path p(v1, *e, v2);
+  ASSERT_EQ(EvaluateFunction("VALUETYPE", p).ValueString(), "PATH");
+}
+
 TEST_F(FunctionTest, Labels) {
   ASSERT_THROW(EvaluateFunction("LABELS"), QueryRuntimeException);
   ASSERT_TRUE(EvaluateFunction("LABELS", TypedValue()).IsNull());