From 47c1cd6e3d0dff4e63fc2136baf3649bc105690c Mon Sep 17 00:00:00 2001
From: florijan <florijan@memgraph.io>
Date: Wed, 9 Aug 2017 11:48:36 +0200
Subject: [PATCH] Degree(Vertex) function added

Summary:
- added only one function for getting the total (in + out) vertex degree, it's required for the Ravelin use-case
- specific `degree_in` and `degree_out` functions can be added as necessary
- also fixed random_graph_generator bug (needed it for testing)

Reviewers: buda, mislav.bradac

Reviewed By: buda, mislav.bradac

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D652
---
 CHANGELOG.md                                   |  1 +
 docs/user_technical/open-cypher.md             |  1 +
 .../interpret/awesome_memgraph_functions.cpp   | 17 +++++++++++++++++
 src/utils/random_graph_generator.hpp           |  1 +
 .../memgraph_V1/features/functions.feature     | 11 +++++++++++
 tests/unit/query_expression_evaluator.cpp      | 18 ++++++++++++++++++
 6 files changed, 49 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd9139834..efed40cc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 * Support for `all` function in openCypher.
 * User specified transaction execution timeout.
 * Support for query parameters (except for parameters in place of property maps).
+* `degree` function added.
 
 ## v0.6.0
 
diff --git a/docs/user_technical/open-cypher.md b/docs/user_technical/open-cypher.md
index 705064b7a..13c7db593 100644
--- a/docs/user_technical/open-cypher.md
+++ b/docs/user_technical/open-cypher.md
@@ -404,6 +404,7 @@ functions.
  `coalesce`   | Returns the first non null argument.
  `startNode`  | Returns the starting node of an edge.
  `endNode`    | Returns the destination node of an edge.
+ `degree`     | Returns the number of edges (both incoming and outgoing) of a node.
  `head`       | Returns the first element of a list.
  `last`       | Returns the last element of a list.
  `properties` | Returns the properties of a node or an edge.
diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp
index 49ea09c5d..6992afb24 100644
--- a/src/query/interpret/awesome_memgraph_functions.cpp
+++ b/src/query/interpret/awesome_memgraph_functions.cpp
@@ -153,6 +153,22 @@ TypedValue StartNode(const std::vector<TypedValue> &args, GraphDbAccessor &) {
   }
 }
 
+TypedValue Degree(const std::vector<TypedValue> &args, GraphDbAccessor &) {
+  if (args.size() != 1U) {
+    throw QueryRuntimeException("degree requires one argument");
+  }
+  switch (args[0].type()) {
+    case TypedValue::Type::Null:
+      return TypedValue::Null;
+    case TypedValue::Type::Vertex: {
+      auto &vertex = args[0].Value<VertexAccessor>();
+      return static_cast<int64_t>(vertex.out_degree() + vertex.in_degree());
+    }
+    default:
+      throw QueryRuntimeException("degree called with incompatible type");
+  }
+}
+
 TypedValue ToBoolean(const std::vector<TypedValue> &args, GraphDbAccessor &) {
   if (args.size() != 1U) {
     throw QueryRuntimeException("toBoolean requires one argument");
@@ -492,6 +508,7 @@ NameToFunction(const std::string &function_name) {
   if (function_name == "PROPERTIES") return Properties;
   if (function_name == "SIZE") return Size;
   if (function_name == "STARTNODE") return StartNode;
+  if (function_name == "DEGREE") return Degree;
   if (function_name == "TOBOOLEAN") return ToBoolean;
   if (function_name == "TOFLOAT") return ToFloat;
   if (function_name == "TOINTEGER") return ToInteger;
diff --git a/src/utils/random_graph_generator.hpp b/src/utils/random_graph_generator.hpp
index 7042edead..9e8fe83a9 100644
--- a/src/utils/random_graph_generator.hpp
+++ b/src/utils/random_graph_generator.hpp
@@ -151,6 +151,7 @@ class RandomGraphGenerator {
     auto property = dba->property(prop_name);
     for (VertexAccessor va : dba->vertices(false))
       if (predicate(va)) va.PropsSet(property, value_generator());
+    dba->commit();
   }
 
  private:
diff --git a/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature b/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature
index a71cb7829..5399c4ce6 100644
--- a/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature
+++ b/tests/qa/tck_engine/tests/memgraph_V1/features/functions.feature
@@ -487,6 +487,17 @@ Feature: Functions
             | n    | a | b |
             | null | 3 | 2 |
 
+    Scenario: Degree test:
+        When executing query:
+            """
+            CREATE (a)-[:Type]->(b)<-[:Type]-(c)
+            RETURN DEGREE(a) AS da, DEGREE(b) AS db, DEGREE(null) AS dn
+            """
+        Then the result should be:
+            | da | db | dn   |
+            | 1  | 2  | null |
+
+
     Scenario: Last test:
         When executing query:
             """
diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp
index 5a137d902..801bebff0 100644
--- a/tests/unit/query_expression_evaluator.cpp
+++ b/tests/unit/query_expression_evaluator.cpp
@@ -768,6 +768,24 @@ TEST(ExpressionEvaluator, FunctionStartNode) {
   ASSERT_THROW(EvaluateFunction("STARTNODE", {2}), QueryRuntimeException);
 }
 
+TEST(ExpressionEvaluator, FunctionDegree) {
+  ASSERT_THROW(EvaluateFunction("DEGREE", {}), QueryRuntimeException);
+  ASSERT_EQ(EvaluateFunction("DEGREE", {TypedValue::Null}).type(),
+            TypedValue::Type::Null);
+  Dbms dbms;
+  auto dba = dbms.active();
+  auto v1 = dba->insert_vertex();
+  auto v2 = dba->insert_vertex();
+  auto v3 = dba->insert_vertex();
+  auto e12 = dba->insert_edge(v1, v2, dba->edge_type("t"));
+  dba->insert_edge(v3, v2, dba->edge_type("t"));
+  ASSERT_EQ(EvaluateFunction("DEGREE", {v1}).Value<int64_t>(), 1);
+  ASSERT_EQ(EvaluateFunction("DEGREE", {v2}).Value<int64_t>(), 2);
+  ASSERT_EQ(EvaluateFunction("DEGREE", {v3}).Value<int64_t>(), 1);
+  ASSERT_THROW(EvaluateFunction("DEGREE", {2}), QueryRuntimeException);
+  ASSERT_THROW(EvaluateFunction("DEGREE", {e12}), QueryRuntimeException);
+}
+
 TEST(ExpressionEvaluator, FunctionToBoolean) {
   ASSERT_THROW(EvaluateFunction("TOBOOLEAN", {}), QueryRuntimeException);
   ASSERT_EQ(EvaluateFunction("TOBOOLEAN", {TypedValue::Null}).type(),